feat: refine admin forms, attendance and finance workflows

Squash merge PR #23.

Included changes:
- complete occupancy check-in required fields/default payload
- improve responsive admin management pages
- fix attendance edge cases and attendance period config
- refine wallet/finance-related workflow handling

Checks:
- npm run typecheck -w apps/admin
- npm run typecheck -w apps/server
This commit is contained in:
2026-07-18 12:54:10 +00:00
parent 92d303ed01
commit 375c7ec60b
64 changed files with 5169 additions and 2404 deletions

View File

@@ -101,87 +101,94 @@ const TeachersPage: React.FC = () => {
}
};
const columns = useMemo(() => [
{ title: '姓名', dataIndex: 'name', key: 'name', width: 120 },
{ title: '用户名', dataIndex: 'username', key: 'username', width: 130 },
{
title: '角色',
dataIndex: 'roles',
key: 'roles',
width: 220,
render: (roles: TeacherRow['roles']) =>
roles.map((r) => <Tag key={r.code}>{ROLE_LABELS[r.code] || r.name}</Tag>),
},
{
title: '任课班级',
dataIndex: 'classAssignments',
key: 'classes',
width: 200,
render: (ca: TeacherRow['classAssignments']) =>
ca?.length
? ca.map((a, i) => (
<Tag key={i}>
{a.className || '-'}
{a.subject ? ` (${a.subject})` : ''}
</Tag>
))
: '-',
},
{
title: '科目',
dataIndex: 'profile',
key: 'subjects',
width: 130,
render: (p: TeacherRow['profile']) => p?.subjects?.join('、') || '-',
},
{
title: '入职日期',
dataIndex: 'profile',
key: 'joinedAt',
width: 110,
render: (p: TeacherRow['profile']) => p?.joinedAt || '-',
},
{
title: '状态',
dataIndex: 'isActive',
key: 'status',
width: 90,
render: (v: boolean) => <Tag color={v ? 'green' : 'red'}>{v ? '在职' : '停用'}</Tag>,
},
{
title: '最后登录',
dataIndex: 'lastLoginAt',
key: 'login',
width: 160,
render: (v: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-'),
},
{
title: '操作',
key: 'actions',
width: 100,
render: (_: unknown, r: TeacherRow) => (
<Button
size="small"
icon={<EditOutlined />}
onClick={() => {
setProfileModal(r);
form.setFieldsValue({
subjects: r.profile?.subjects || [],
joinedAt: r.profile?.joinedAt ? dayjs(r.profile.joinedAt) : null,
qualifications: r.profile?.qualifications || '',
});
}}
>
</Button>
),
},
], []);
const columns = useMemo(
() => [
{ title: '名', dataIndex: 'name', key: 'name', width: 120 },
{ title: '用户名', dataIndex: 'username', key: 'username', width: 130 },
{
title: '角色',
dataIndex: 'roles',
key: 'roles',
width: 220,
render: (roles: TeacherRow['roles']) =>
roles.map((r) => <Tag key={r.code}>{ROLE_LABELS[r.code] || r.name}</Tag>),
},
{
title: '任课班级',
dataIndex: 'classAssignments',
key: 'classes',
width: 200,
render: (ca: TeacherRow['classAssignments']) =>
ca?.length
? ca.map((a, i) => (
<Tag key={i}>
{a.className || '-'}
{a.subject ? ` (${a.subject})` : ''}
</Tag>
))
: '-',
},
{
title: '科目',
dataIndex: 'profile',
key: 'subjects',
width: 130,
render: (p: TeacherRow['profile']) => p?.subjects?.join('、') || '-',
},
{
title: '入职日期',
dataIndex: 'profile',
key: 'joinedAt',
width: 110,
render: (p: TeacherRow['profile']) => p?.joinedAt || '-',
},
{
title: '状态',
dataIndex: 'isActive',
key: 'status',
width: 90,
render: (v: boolean) => <Tag color={v ? 'green' : 'red'}>{v ? '在职' : '停用'}</Tag>,
},
{
title: '最后登录',
dataIndex: 'lastLoginAt',
key: 'login',
width: 160,
render: (v: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-'),
},
{
title: '操作',
key: 'actions',
width: 100,
render: (_: unknown, r: TeacherRow) => (
<Button
size="small"
icon={<EditOutlined />}
onClick={() => {
setProfileModal(r);
form.setFieldsValue({
subjects: r.profile?.subjects || [],
joinedAt: r.profile?.joinedAt ? dayjs(r.profile.joinedAt) : null,
qualifications: r.profile?.qualifications || '',
});
}}
>
</Button>
),
},
],
[],
);
return (
<div>
<h2 style={{ marginBottom: 16 }}></h2>
<Space style={{ marginBottom: 16 }} wrap className="responsive-toolbar responsive-toolbar--single">
<Space
style={{ marginBottom: 16 }}
wrap
className="responsive-toolbar responsive-toolbar--single"
>
<Input.Search
placeholder="搜索姓名/用户名"
allowClear