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

195 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import {
Alert,
Avatar,
Button,
Drawer,
Form,
Input,
Modal,
Segmented,
Select,
} from 'antd';
import { UndoOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import {
ADMIN_CORRECTION_OPTIONS,
AttendanceStatusTag,
displayAttendanceStatus,
type AdminStudentPanel,
type AttendancePeriodConfigItem,
type AttendanceRecordItem,
} from './AttendanceAdmin.helpers';
export const PeriodConfigModal: React.FC<{
open: boolean;
form: ReturnType<typeof Form.useForm<{ periods: AttendancePeriodConfigItem[] }>>[0];
onOk: () => void;
onCancel: () => void;
onReset: () => void;
}> = ({ open, form, onOk, onCancel, onReset }) => {
return (
<Modal
open={open}
title="考勤时段配置"
okText="保存配置"
width={860}
className="attendance-period-modal"
onOk={onOk}
onCancel={onCancel}
footer={(_, { OkBtn, CancelBtn }) => (
<>
<Button icon={<UndoOutlined />} onClick={onReset}>
</Button>
<CancelBtn />
<OkBtn />
</>
)}
>
<Alert
type="info"
showIcon
title="按课程开始时间自动匹配时段"
description="默认07:30-08:30 早自习09:00-12:00 早课14:00-17:00 晚课18:30-21:00 晚自习。"
style={{ marginBottom: 16 }}
/>
<Form form={form} layout="vertical">
<Form.List name="periods">
{(fields, { add, remove }) => (
<div className="attendance-period-editor">
{fields.map((field) => (
<div className="attendance-period-row" key={field.key}>
<Form.Item name={[field.name, 'label']} label="显示名称" rules={[{ required: true, message: '请输入名称' }]}>
<Input placeholder="早课" />
</Form.Item>
<Form.Item name={[field.name, 'periodKey']} label="类型标识" rules={[{ required: true, message: '请输入标识' }]}>
<Input placeholder="morning" />
</Form.Item>
<Form.Item name={[field.name, 'startTime']} label="开始时间" rules={[{ required: true, message: '请选择开始时间' }]}>
<Input type="time" />
</Form.Item>
<Form.Item name={[field.name, 'endTime']} label="结束时间" rules={[{ required: true, message: '请选择结束时间' }]}>
<Input type="time" />
</Form.Item>
<Form.Item name={[field.name, 'enabled']} label="状态">
<Select
options={[
{ value: true, label: '启用' },
{ value: false, label: '停用' },
]}
/>
</Form.Item>
<Button danger className="attendance-period-row__delete" onClick={() => remove(field.name)}>
</Button>
</div>
))}
<Button
block
type="dashed"
className="attendance-period-add"
onClick={() =>
add({
periodKey: '',
label: '',
startTime: '00:00',
endTime: '00:30',
enabled: true,
})
}
>
</Button>
</div>
)}
</Form.List>
</Form>
</Modal>
);
};
export const StudentDetailDrawer: React.FC<{
student: AdminStudentPanel | null;
isMobile: boolean;
canEdit: boolean;
sessionMap: Record<string, string>;
correctingRecordId: number | null;
sortAttendanceRecords: (items: readonly AttendanceRecordItem[]) => AttendanceRecordItem[];
onClose: () => void;
onUpdateAdminRecordStatus: (record: AttendanceRecordItem, nextStatus: string) => void;
}> = ({
student,
isMobile,
canEdit,
sessionMap,
correctingRecordId,
sortAttendanceRecords,
onClose,
onUpdateAdminRecordStatus,
}) => {
return (
<Drawer
open={Boolean(student)}
onClose={onClose}
size={isMobile ? '100%' : 520}
title="学生考勤明细"
className="student-detail-drawer"
>
{student && (
<div className="student-detail-panel">
<section className="student-detail-profile">
<Avatar size={54}>{student.studentName.slice(0, 1)}</Avatar>
<div>
<h4>{student.studentName}</h4>
<p>
{student.className}
{student.studentNo ? ` · 学号 ${student.studentNo}` : ''}
</p>
</div>
<div className="student-detail-rate">
<strong>{student.rate}%</strong>
<span></span>
</div>
</section>
<section className="student-detail-rates">
{sortAttendanceRecords(student.records).map((record) => {
const normal = record.status === 'present';
return (
<div key={record.id}>
<strong>{normal ? '100%' : '0%'}</strong>
<span>{sessionMap[record.session] || record.session}</span>
</div>
);
})}
</section>
<section className="student-detail-section">
<h5></h5>
<div className="student-detail-timeline">
{sortAttendanceRecords(student.records).map((record) => (
<div key={record.id}>
<span>{sessionMap[record.session] || record.session}</span>
<AttendanceStatusTag status={displayAttendanceStatus(record.status)} />
<strong>
{record.punchTime ? dayjs(record.punchTime).format('HH:mm:ss') : '未记录'}
</strong>
{canEdit && (
<Segmented
size="small"
className="attendance-correction-segment"
options={ADMIN_CORRECTION_OPTIONS}
value={displayAttendanceStatus(record.status)}
disabled={correctingRecordId === record.id}
onChange={(value) => void onUpdateAdminRecordStatus(record, String(value))}
/>
)}
</div>
))}
</div>
</section>
</div>
)}
</Drawer>
);
};