import React, { useCallback, useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { Button, Card, Col, Empty, Row, Spin, Tag, Tooltip } from 'antd'; import { ArrowRightOutlined, CheckCircleFilled, ClockCircleOutlined, ReloadOutlined, ScheduleOutlined, TeamOutlined, } from '@ant-design/icons'; import dayjs from 'dayjs'; import api from '../../api'; import { QueryErrorState } from '../../components/QueryState'; import { canPullAttendance, getSchedulePhase, type SchedulePhase, } from './attendance-workspace'; import LessonAttendanceDetail from './LessonAttendanceDetail'; import type { LessonAttendanceSchedule } from './types'; type TodaySchedule = LessonAttendanceSchedule; interface AssignedClass { classId: number; className: string; classCode: string; roleType: string; subject: string; } interface TeacherWorkspaceData { assignedClasses: AssignedClass[]; todaySchedules: TodaySchedule[]; } export const TeacherAttendanceWorkspace: React.FC<{ canCreate: boolean }> = ({ canCreate }) => { const [selectedSchedule, setSelectedSchedule] = useState(null); const { data: workspace, isFetching, isPending, isError, refetch, } = useQuery({ queryKey: ['attendance', 'workspace'], queryFn: async () => { return await api.get('/rbac/teacher-workspace'); }, }); // isPending 覆盖自动重试的退避窗口,避免「加载失败/重试中」短暂闪现为空态 const loading = isPending || isFetching; const loadWorkspace = useCallback(() => refetch(), [refetch]); const classNameById = useMemo( () => new Map(workspace?.assignedClasses.map((item) => [item.classId, item.className]) ?? []), [workspace], ); const openAttendance = useCallback((schedule: TodaySchedule) => { setSelectedSchedule(schedule); }, []); const now = new Date(); const schedules = workspace?.todaySchedules ?? []; const startedCount = schedules.filter((item) => canPullAttendance(getSchedulePhase(item.startTime, item.endTime, now)), ).length; const nextSchedule = schedules.find( (item) => getSchedulePhase(item.startTime, item.endTime, now) !== 'ended', ); return (

今天,从课程开始

{dayjs().format('MM月DD日 dddd')} · 课程开始后可查看最新打卡结果;课程截止时系统自动拉取并结算缺勤。

今日课程 {schedules.length}
已开始 {startedCount} 节,可查看考勤
下一节 {nextSchedule ? nextSchedule.startTime : '—'} {nextSchedule?.subject || '今天没有更多课程'}
今日教学节奏

我的课程

课程开始后可拉取钉钉考勤记录
{isError ? ( void loadWorkspace()} /> ) : schedules.length === 0 ? ( 今天还没有课程

请联系教务管理员安排课程。

} /> ) : (
{schedules.map((schedule, index) => { const phase = getSchedulePhase(schedule.startTime, schedule.endTime, now); return ( openAttendance(schedule) : undefined} /> ); })}
)} {canCreate ? ( setSelectedSchedule(null)} /> ) : null} ); }; export const LessonCard: React.FC<{ schedule: TodaySchedule; phase: SchedulePhase; className: string; index: number; onOpen?: () => void; }> = ({ schedule, phase, className, index, onOpen }) => { const phaseMeta = { upcoming: { label: '待上课', icon: , tone: 'upcoming' }, ongoing: { label: '进行中', icon: , tone: 'ongoing' }, ended: { label: '已结束', icon: , tone: 'ended' }, }[phase]; return (
{String(index).padStart(2, '0')}
{schedule.startTime} {schedule.endTime}

{schedule.subject}

{phaseMeta.label}

{className} 教室 {schedule.classroomId}

{phase === 'upcoming' ? ( ) : onOpen ? ( ) : null}
); };