132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
import { Avatar, Segmented, Tag } from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
import dayjs from 'dayjs';
|
|
import EditableCell from '../../components/EditableCell';
|
|
import { getPunchDisplayInfo } from './attendance-workspace';
|
|
import {
|
|
ADMIN_CORRECTION_OPTIONS,
|
|
AttendanceStatusTag,
|
|
displayAttendanceStatus,
|
|
type AttendanceRecordItem,
|
|
} from './AttendanceAdmin.helpers';
|
|
|
|
export interface AttendanceAdminColumnContext {
|
|
isMobile: boolean;
|
|
canEdit: boolean;
|
|
sessionMap: Record<string, string>;
|
|
correctingRecordId: number | null;
|
|
onSaveAdminRecordCell: (
|
|
record: AttendanceRecordItem,
|
|
field: 'status' | 'remark',
|
|
value: unknown,
|
|
) => void;
|
|
onUpdateAdminRecordStatus: (record: AttendanceRecordItem, nextStatus: string) => void;
|
|
}
|
|
|
|
const buildAttendanceAdminDataColumns = (ctx: AttendanceAdminColumnContext) => {
|
|
const { isMobile, canEdit, sessionMap, onSaveAdminRecordCell } = ctx;
|
|
return [
|
|
{
|
|
title: '学生',
|
|
dataIndex: ['student', 'name'],
|
|
fixed: (isMobile ? undefined : 'left') as 'left' | undefined,
|
|
width: 150,
|
|
render: (name: string, record: AttendanceRecordItem) => (
|
|
<div className="student-cell">
|
|
<Avatar size={34}>{name?.slice(0, 1)}</Avatar>
|
|
<div>
|
|
<strong>{name || '-'}</strong>
|
|
<span>{record.class?.name || '未关联班级'}</span>
|
|
</div>
|
|
</div>
|
|
),
|
|
},
|
|
{ title: '日期', dataIndex: 'attendanceDate', width: 120 },
|
|
{
|
|
title: '时段',
|
|
dataIndex: 'session',
|
|
width: 110,
|
|
render: (value: string) => sessionMap[value] || value,
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
width: 105,
|
|
render: (value: string, record: AttendanceRecordItem) => (
|
|
<EditableCell
|
|
value={displayAttendanceStatus(value)}
|
|
editor="select"
|
|
options={ADMIN_CORRECTION_OPTIONS.map((item) => ({
|
|
value: String(item.value),
|
|
label: item.label,
|
|
}))}
|
|
disabled={!canEdit}
|
|
onSave={async (next) => {
|
|
onSaveAdminRecordCell(record, 'status', next);
|
|
}}
|
|
>
|
|
<AttendanceStatusTag status={displayAttendanceStatus(value)} />
|
|
</EditableCell>
|
|
),
|
|
},
|
|
{
|
|
title: '签到来源',
|
|
width: 220,
|
|
render: (_: unknown, record: AttendanceRecordItem) => {
|
|
const info = getPunchDisplayInfo(record);
|
|
if (!info) return <span className="muted-text">—</span>;
|
|
return (
|
|
<div className="punch-device-cell">
|
|
<Tag color={info.machine ? 'green' : 'blue'}>{info.label}</Tag>
|
|
{info.detail && <strong>{info.detail}</strong>}
|
|
{info.time && <span>{dayjs(info.time).format('HH:mm:ss')}</span>}
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: '备注',
|
|
dataIndex: 'remark',
|
|
ellipsis: true,
|
|
render: (value: string | null, record: AttendanceRecordItem) => (
|
|
<EditableCell
|
|
value={value}
|
|
editor="textarea"
|
|
disabled={!canEdit}
|
|
onSave={async (next) => {
|
|
onSaveAdminRecordCell(record, 'remark', next);
|
|
}}
|
|
>
|
|
{value || <span className="muted-text">—</span>}
|
|
</EditableCell>
|
|
),
|
|
},
|
|
] as ColumnsType<AttendanceRecordItem>;
|
|
};
|
|
|
|
const buildAttendanceAdminActionColumn = (ctx: AttendanceAdminColumnContext) => {
|
|
const { isMobile, correctingRecordId, onUpdateAdminRecordStatus } = ctx;
|
|
return {
|
|
title: '操作',
|
|
key: 'action',
|
|
fixed: (isMobile ? undefined : 'right') as 'right' | undefined,
|
|
width: 220,
|
|
render: (_: unknown, record: AttendanceRecordItem) => (
|
|
<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))}
|
|
/>
|
|
),
|
|
};
|
|
};
|
|
|
|
export const buildAttendanceAdminColumns = (ctx: AttendanceAdminColumnContext) => {
|
|
const dataColumns = buildAttendanceAdminDataColumns(ctx);
|
|
const actionColumn = ctx.canEdit ? [buildAttendanceAdminActionColumn(ctx)] : [];
|
|
return [...dataColumns, ...actionColumn] as ColumnsType<AttendanceRecordItem>;
|
|
};
|