import dayjs from 'dayjs'; import type { AttendanceSummary } from './attendance-workspace'; import type { LessonAttendanceRecord } from './types'; export type { AttendanceSummary } from './attendance-workspace'; export const DEFAULT_ATTENDANCE_PERIODS = [ { periodKey: 'morning_reading', label: '早自习', startTime: '07:30', endTime: '08:30', sortOrder: 1, enabled: true }, { periodKey: 'morning', label: '早课', startTime: '09:00', endTime: '12:00', sortOrder: 2, enabled: true }, { periodKey: 'afternoon', label: '晚课', startTime: '14:00', endTime: '17:00', sortOrder: 3, enabled: true }, { periodKey: 'evening_study', label: '晚自习', startTime: '18:30', endTime: '21:00', sortOrder: 4, enabled: true }, ]; export const STATUS_META: Record< string, { label: string; color: string; className: string; short: string } > = { present: { label: '出勤', color: 'success', className: 'is-present', short: '勤' }, absent: { label: '缺勤', color: 'error', className: 'is-absent', short: '缺' }, leave: { label: '请假', color: 'processing', className: 'is-leave', short: '假' }, pending: { label: '待确认', color: 'default', className: 'is-pending', short: '待' }, }; export const ADMIN_CORRECTION_OPTIONS = [ { value: 'present', label: '正常' }, { value: 'leave', label: '请假' }, { value: 'absent', label: '缺勤' }, ]; export interface ClassTeacherOption { userId: number; username: string | null; name: string | null; roleType: string; subject: string | null; } export interface ClassOption { classId: number; className: string; teachers?: ClassTeacherOption[]; } export type AttendanceRecordItem = LessonAttendanceRecord; export interface HistoryScheduleOption { id: number; classId: number; weekDay: number; startTime: string; endTime: string; startDate: string; endDate: string; subject: string; teacherId: number | null; teacherName?: string | null; teacherUsername?: string | null; status: string; } export interface AlertItem { studentId: number; studentName: string; studentNo: string; className: string; type: string; count: number; lastDate: string; } export interface AttendancePeriodConfigItem { id?: number; periodKey: string; label: string; startTime: string; endTime: string; sortOrder: number; enabled: boolean; } export interface DingTalkSyncStatus { lastPulledAt: string | null; action: string | null; username: string | null; detail: string | null; } export const EMPTY_SUMMARY: AttendanceSummary = { total: 0, present: 0, late: 0, absent: 0, leave: 0, pending: 0, }; export function displayAttendanceStatus(status?: string | null): string { return status === 'pending' || !status ? 'absent' : status; } export function getTeacherDisplayName(teacher?: { name?: string | null; username?: string | null; }): string { const name = teacher?.name?.trim(); if (name) return name; return teacher?.username?.trim() || '未设置'; } export function formatTeacherNames( teachers: readonly { name?: string | null; username?: string | null }[], ): string { const names = [ ...new Set( teachers .map((teacher) => getTeacherDisplayName(teacher)) .filter((name) => name && name !== '未设置'), ), ]; return names.length > 0 ? names.join('、') : '未设置'; } export function AttendanceStatusTag({ status }: { status: string }) { const displayStatus = displayAttendanceStatus(status); const meta = STATUS_META[displayStatus] ?? { label: displayStatus, color: 'default', className: 'is-absent', short: '?', }; return ( {meta.label} ); } export interface AdminStudentPanel { key: string; studentId: number; studentName: string; studentNo: string; className: string; records: AttendanceRecordItem[]; statusBySession: Partial>; primaryStatus: string; rate: number; latestDate: string; } export const ADMIN_METRIC_META = [ { key: 'all', label: '出勤率', short: '率' }, { key: 'present', label: '正常', short: '正常' }, { key: 'leave', label: '请假', short: '请假' }, { key: 'absent', label: '缺勤', short: '缺勤' }, ]; function pickPrimaryStatus(records: AttendanceRecordItem[]) { const priority = ['absent', 'leave', 'present']; return ( priority.find((item) => records.some((record) => displayAttendanceStatus(record.status) === item), ) || 'absent' ); } export function buildAdminStudentPanels(records: AttendanceRecordItem[]): AdminStudentPanel[] { const map = new Map(); for (const record of records) { const current = map.get(record.studentId) ?? { key: String(record.studentId), studentId: record.studentId, studentName: record.student?.name || '未知学生', studentNo: record.student?.studentNo?.trim() || '', className: record.class?.name || '未关联班级', records: [], statusBySession: {}, primaryStatus: 'absent', rate: 0, latestDate: record.attendanceDate, }; current.records.push(record); if (!current.statusBySession[record.session]) current.statusBySession[record.session] = record; if (dayjs(record.attendanceDate).isAfter(dayjs(current.latestDate))) { current.latestDate = record.attendanceDate; } map.set(record.studentId, current); } return Array.from(map.values()).map((item) => { const checked = item.records.filter((record) => record.status === 'present').length; return { ...item, primaryStatus: pickPrimaryStatus(item.records), rate: item.records.length > 0 ? Math.round((checked / item.records.length) * 100) : 0, }; }); }