feat: improve attendance scheduling and API validation
This commit is contained in:
@@ -39,6 +39,7 @@ interface ClassScheduleItem {
|
||||
weekDay: number;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
attendanceAdvanceMinutes: number;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
subject: string;
|
||||
@@ -353,6 +354,7 @@ const ClassDetailPage: React.FC = () => {
|
||||
{ title: '教室', dataIndex: 'classroomName', render: (v: string | null) => v || '-' },
|
||||
{ title: '星期', dataIndex: 'weekDay', render: (v: number) => WEEK_DAY_MAP[v] || v },
|
||||
{ title: '时间', render: (_: unknown, r: ClassScheduleItem) => `${r.startTime} - ${r.endTime}` },
|
||||
{ title: '签到窗口', render: (_: unknown, r: ClassScheduleItem) => `课前 ${r.attendanceAdvanceMinutes ?? 30} 分钟至下课` },
|
||||
{ title: '日期范围', render: (_: unknown, r: ClassScheduleItem) => `${r.startDate} ~ ${r.endDate}` },
|
||||
{ title: '科目', dataIndex: 'subject' },
|
||||
{ title: '类型', dataIndex: 'scheduleType', render: (v: string) => SCHEDULE_TYPE_MAP[v] || v },
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Modal,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
DatePicker,
|
||||
TimePicker,
|
||||
Popconfirm,
|
||||
@@ -53,6 +54,7 @@ interface ClassScheduleItem {
|
||||
weekDay: number;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
attendanceAdvanceMinutes: number;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
subject: string;
|
||||
@@ -351,7 +353,7 @@ const SchedulesPage: React.FC = () => {
|
||||
setEditingSchedule(null);
|
||||
setModalMode('create');
|
||||
form.resetFields();
|
||||
form.setFieldsValue({ classroomId, weekDay });
|
||||
form.setFieldsValue({ classroomId, weekDay, attendanceAdvanceMinutes: 30 });
|
||||
setModalOpen(true);
|
||||
}
|
||||
};
|
||||
@@ -960,9 +962,28 @@ const SchedulesPage: React.FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="attendanceAdvanceMinutes"
|
||||
label="课前签到时间"
|
||||
tooltip="从上课前指定分钟开始,到下课时间结束;期间任意上班或下班打卡都计为出勤"
|
||||
initialValue={30}
|
||||
rules={[{ required: true, message: '请设置课前签到时间' }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={1440}
|
||||
step={5}
|
||||
addonAfter="分钟"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="例如 30"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="timeRange"
|
||||
label="上课时段"
|
||||
tooltip="同一教室的前后两节排课必须至少间隔10分钟"
|
||||
extra="系统按10分钟选择时间,并为相邻排课强制预留至少10分钟。"
|
||||
rules={[{ required: true, message: '请选择时段' }]}
|
||||
>
|
||||
<TimePicker.RangePicker
|
||||
@@ -1008,6 +1029,7 @@ const SchedulesPage: React.FC = () => {
|
||||
weekDay:
|
||||
selectedCell?.weekDay ?? (selectedDate ? selectedDate.day() || 7 : undefined),
|
||||
dateRange: selectedDate ? [selectedDate, selectedDate] : undefined,
|
||||
attendanceAdvanceMinutes: 30,
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -1054,6 +1076,12 @@ const SchedulesPage: React.FC = () => {
|
||||
<strong>时段:</strong>
|
||||
{s.startTime} ~ {s.endTime}
|
||||
</div>
|
||||
{!isMaskedSchedule(s) && (
|
||||
<div>
|
||||
<strong>签到窗口:</strong>
|
||||
课前 {s.attendanceAdvanceMinutes ?? 30} 分钟至下课
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<strong>日期:</strong>
|
||||
{s.startDate} ~ {s.endDate}
|
||||
|
||||
@@ -16,11 +16,13 @@ describe('schedule edit form mapping', () => {
|
||||
startDate: '2026-07-01',
|
||||
endDate: '2026-07-31',
|
||||
notes: '需要投影设备',
|
||||
attendanceAdvanceMinutes: 45,
|
||||
});
|
||||
|
||||
expect(values.classroomId).toBe(1);
|
||||
expect(values.weekDay).toBe(5);
|
||||
expect(values.notes).toBe('需要投影设备');
|
||||
expect(values.attendanceAdvanceMinutes).toBe(45);
|
||||
expect(values.timeRange.map((item) => item.format('HH:mm'))).toEqual(['14:00', '18:00']);
|
||||
expect(values.dateRange.map((item) => item.format('YYYY-MM-DD'))).toEqual([
|
||||
'2026-07-01',
|
||||
@@ -39,6 +41,7 @@ describe('schedule edit form mapping', () => {
|
||||
timeRange: [dayjs('2026-01-01 13:30'), dayjs('2026-01-01 17:20')],
|
||||
dateRange: [dayjs('2026-08-01'), dayjs('2026-08-31')],
|
||||
notes: ' 临时调整教室 ',
|
||||
attendanceAdvanceMinutes: 20,
|
||||
}),
|
||||
).toEqual({
|
||||
classId: 1,
|
||||
@@ -51,6 +54,7 @@ describe('schedule edit form mapping', () => {
|
||||
startDate: '2026-08-01',
|
||||
endDate: '2026-08-31',
|
||||
notes: '临时调整教室',
|
||||
attendanceAdvanceMinutes: 20,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -67,6 +71,7 @@ describe('schedule notes normalization', () => {
|
||||
timeRange: [dayjs('2026-01-01 13:30'), dayjs('2026-01-01 17:20')],
|
||||
dateRange: [dayjs('2026-08-01'), dayjs('2026-08-31')],
|
||||
notes: ' ',
|
||||
attendanceAdvanceMinutes: 30,
|
||||
}).notes,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface ScheduleFormValues {
|
||||
subject: string;
|
||||
teacherId?: number;
|
||||
notes?: string;
|
||||
attendanceAdvanceMinutes: number;
|
||||
timeRange: [Dayjs, Dayjs];
|
||||
dateRange: [Dayjs, Dayjs];
|
||||
}
|
||||
@@ -19,6 +20,7 @@ export interface EditableSchedule {
|
||||
subject: string;
|
||||
teacherId: number | null;
|
||||
notes?: string | null;
|
||||
attendanceAdvanceMinutes?: number | null;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
startDate: string;
|
||||
@@ -32,6 +34,7 @@ export const scheduleToFormValues = (schedule: EditableSchedule): ScheduleFormVa
|
||||
subject: schedule.subject,
|
||||
teacherId: schedule.teacherId ?? undefined,
|
||||
notes: schedule.notes ?? undefined,
|
||||
attendanceAdvanceMinutes: schedule.attendanceAdvanceMinutes ?? 30,
|
||||
timeRange: [dayjs(`2000-01-01 ${schedule.startTime}`), dayjs(`2000-01-01 ${schedule.endTime}`)],
|
||||
dateRange: [dayjs(schedule.startDate), dayjs(schedule.endDate)],
|
||||
});
|
||||
@@ -43,6 +46,7 @@ export const buildSchedulePayload = (values: ScheduleFormValues) => ({
|
||||
subject: values.subject,
|
||||
teacherId: values.teacherId,
|
||||
notes: values.notes?.trim() || undefined,
|
||||
attendanceAdvanceMinutes: values.attendanceAdvanceMinutes,
|
||||
startTime: values.timeRange[0].format('HH:mm'),
|
||||
endTime: values.timeRange[1].format('HH:mm'),
|
||||
startDate: values.dateRange[0].format('YYYY-MM-DD'),
|
||||
|
||||
Reference in New Issue
Block a user