import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Alert, Avatar, Button, Drawer, Empty, Input, Progress, Select, Table, Tag } from 'antd';
import dayjs from 'dayjs';
import api from '../../api';
import { message } from '../../ui/app-message';
import {
filterLessonAttendanceRecords,
getPunchDisplayInfo,
summarizeLessonCheckins,
type LessonAttendanceFilter,
} from './attendance-workspace';
import type { LessonAttendanceRecord, LessonAttendanceSchedule } from './types';
interface LessonAttendanceSession {
id: number;
scheduleId: number;
classId: number;
lessonDate: string;
status: 'in_progress' | 'completed';
}
interface LessonAttendanceResponse {
schedule: LessonAttendanceSchedule;
session: LessonAttendanceSession | null;
records: LessonAttendanceRecord[];
}
interface LessonAttendanceDetailProps {
schedule: LessonAttendanceSchedule | null;
className: string;
onClose: () => void;
}
function AttendanceStatus({ status }: { status: string }) {
const checkedIn = status === 'present' || status === 'late';
return (
{checkedIn ? '出勤' : '缺勤'}
);
}
function LessonSummary({ records }: { records: readonly LessonAttendanceRecord[] }) {
const summary = summarizeLessonCheckins(records);
const rate = summary.total > 0 ? Math.round((summary.checkedIn / summary.total) * 100) : 0;
return (
勤
{summary.checkedIn}
已打卡
缺
{summary.notCheckedIn}
未打卡
);
}
const LessonAttendanceDetail: React.FC = ({
schedule,
className,
onClose,
}) => {
const [loadedSchedule, setLoadedSchedule] = useState(null);
const [session, setSession] = useState(null);
const [records, setRecords] = useState([]);
const [loading, setLoading] = useState(false);
const [keyword, setKeyword] = useState('');
const [filter, setFilter] = useState('all');
useEffect(() => {
if (!schedule) return;
let cancelled = false;
setLoadedSchedule(schedule);
setSession(null);
setRecords([]);
setKeyword('');
setFilter('all');
setLoading(true);
const date = dayjs().format('YYYY-MM-DD');
void api
.post(`/attendance-lessons/schedules/${schedule.id}/pull`, {
date,
})
.then((data) => {
if (cancelled) return;
setLoadedSchedule(data.schedule);
setSession(data.session);
setRecords(data.records);
message.success('钉钉打卡已更新;课程截止后系统将自动结算');
})
.catch((error: unknown) => {
if (cancelled) return;
message.error((error as { message?: string })?.message || '加载本节课考勤失败');
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [schedule]);
const updateRecord = useCallback(async (record: LessonAttendanceRecord, status: string) => {
const previous = record.status;
setRecords((items) =>
items.map((item) => (item.id === record.id ? { ...item, status } : item)),
);
try {
await api.put(`/attendance-records/${record.id}`, { status });
} catch (error: unknown) {
setRecords((items) =>
items.map((item) => (item.id === record.id ? { ...item, status: previous } : item)),
);
message.error((error as { message?: string })?.message || '更新考勤失败');
}
}, []);
const filteredRecords = useMemo(
() => filterLessonAttendanceRecords(records, keyword, filter),
[filter, keyword, records],
);
const completed = session?.status === 'completed';
const displayedSchedule = loadedSchedule ?? schedule;
return (
LESSON ATTENDANCE
{displayedSchedule?.subject || '课程考勤'}
{className} · {displayedSchedule?.startTime}–{displayedSchedule?.endTime} ·{' '}
{dayjs().format('YYYY-MM-DD')}
{session && (
)}
setKeyword(event.target.value)}
className="lesson-record-search"
/>
rowKey="id"
loading={loading}
dataSource={filteredRecords}
pagination={false}
locale={{
emptyText: (
),
}}
columns={[
{
title: '学生',
dataIndex: ['student', 'name'],
render: (name: string) => (
{name?.slice(0, 1)}
{name || '-'}
),
},
{
title: '考勤结果',
dataIndex: 'status',
width: 230,
render: (value: string, record) => {
const checkedIn = value === 'present' || value === 'late';
return (
);
},
},
{
title: '当前状态',
dataIndex: 'status',
width: 105,
render: (value: string) => ,
},
{
title: '打卡设备',
width: 220,
render: (_: unknown, record) => {
const info = getPunchDisplayInfo(record);
if (!info) return —;
return (
{info.label}
{info.detail && {info.detail}}
{info.time && {dayjs(info.time).format('HH:mm:ss')}}
);
},
},
{
title: '备注',
dataIndex: 'remark',
render: (value: string | null) => value || —,
},
]}
/>
);
};
export default LessonAttendanceDetail;