Files
gongxue-base/apps/admin/src/pages/Attendance/AttendanceAdmin.helpers.tsx

200 lines
5.7 KiB
TypeScript

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 (
<span className={`attendance-status ${meta.className}`}>
<span className="attendance-status__dot" />
{meta.label}
</span>
);
}
export interface AdminStudentPanel {
key: string;
studentId: number;
studentName: string;
studentNo: string;
className: string;
records: AttendanceRecordItem[];
statusBySession: Partial<Record<string, AttendanceRecordItem>>;
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<number, AdminStudentPanel>();
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,
};
});
}