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

165 lines
5.3 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 { Card, Empty, Input, Spin, Table } from 'antd';
import { ExportOutlined } from '@ant-design/icons';
import PermissionButton from '../../components/PermissionButton';
import {
ADMIN_METRIC_META,
STATUS_META,
displayAttendanceStatus,
type AdminStudentPanel,
type AttendanceRecordItem,
} from './AttendanceAdmin.helpers';
export const AttendanceAdminWorkspace: React.FC<{
metricFilter: string;
studentSearch: string;
onSearchChange: (value: string) => void;
onExport: () => void;
visibleStudents: AdminStudentPanel[];
loading: boolean;
selectedStudentId?: number;
onSelectStudent: (student: AdminStudentPanel) => void;
sortAttendanceRecords: (items: readonly AttendanceRecordItem[]) => AttendanceRecordItem[];
sessionMap: Record<string, string>;
records: AttendanceRecordItem[];
columns: any[];
page: number;
pageSize: number;
total: number;
onPageChange: (page: number, pageSize: number) => void;
}> = ({
metricFilter,
studentSearch,
onSearchChange,
onExport,
visibleStudents,
loading,
selectedStudentId,
onSelectStudent,
sortAttendanceRecords,
sessionMap,
records,
columns,
page,
pageSize,
total,
onPageChange,
}) => {
return (
<>
<section className="student-workspace">
<header className="student-workspace-header">
<div>
<h3></h3>
<span>
{metricFilter === 'all'
? `显示全部 ${visibleStudents.length} 名学生`
: `筛出 ${
ADMIN_METRIC_META.find((item) => item.key === metricFilter)?.label || ''
}相关 ${visibleStudents.length} 名学生`}
</span>
</div>
<div className="student-workspace-tools">
<Input.Search
allowClear
placeholder="搜索姓名或学号"
value={studentSearch}
onChange={(event) => onSearchChange(event.target.value)}
/>
<PermissionButton
permission="attendance:export"
icon={<ExportOutlined />}
onClick={onExport}
>
</PermissionButton>
</div>
</header>
<div className="student-legend">
<span>
<i className="is-present" />
</span>
<span>
<i className="is-leave" />
</span>
<span>
<i className="is-absent" />
</span>
<em>/</em>
</div>
<Spin spinning={loading}>
{visibleStudents.length === 0 ? (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="未找到匹配学生,请调整筛选条件或搜索关键词"
/>
) : (
<div className="student-card-grid">
{visibleStudents.map((student) => (
<button
type="button"
key={student.key}
className={`student-attendance-card ${
selectedStudentId === student.studentId ? 'selected' : ''
}`}
onClick={() => onSelectStudent(student)}
>
<span className="student-attendance-head">
<strong>{student.studentName}</strong>
{student.studentNo && <small> {student.studentNo}</small>}
</span>
<span className="student-status-blocks">
{sortAttendanceRecords(student.records).map((record) => {
const currentStatus = displayAttendanceStatus(record.status);
const meta = STATUS_META[currentStatus] ?? STATUS_META.absent;
const sessionLabel = sessionMap[record.session] || record.session;
return (
<span
key={record.id}
className={`student-status-block ${meta.className}`}
title={`${sessionLabel}${meta.label}`}
>
{meta.label === '出勤' ? '正常' : meta.label}
</span>
);
})}
</span>
</button>
))}
</div>
)}
</Spin>
</section>
<Card className="student-record-card" bordered={false} title="原始考勤明细">
<Table<AttendanceRecordItem>
rowKey="id"
columns={columns}
dataSource={records}
loading={loading}
scroll={{ x: 'max-content' }}
pagination={{
current: page,
pageSize,
total,
showSizeChanger: true,
showTotal: (value) => `${value} 条历史记录`,
onChange: onPageChange,
}}
locale={{
emptyText: (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="当前条件下没有历史考勤记录"
/>
),
}}
/>
</Card>
</>
);
};