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

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>;
};