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:
@@ -1,24 +1,17 @@
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import { Table, Modal, Form, Input, Select, Switch, Space, Tag, Popconfirm } from 'antd';
|
||||
import {
|
||||
Table,
|
||||
Modal,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
Switch,
|
||||
Space,
|
||||
Tag,
|
||||
Popconfirm,
|
||||
} from 'antd';
|
||||
import { PlusOutlined, EditOutlined, KeyOutlined, IdcardOutlined, InboxOutlined } from '@ant-design/icons';
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
KeyOutlined,
|
||||
IdcardOutlined,
|
||||
InboxOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import api from '../../api';
|
||||
import PermissionButton from '../../components/PermissionButton';
|
||||
import { message } from '../../ui/app-message';
|
||||
import {
|
||||
userProfileResponseToFormValues,
|
||||
type UserProfileResponse,
|
||||
} from './user-profile-form';
|
||||
import { userProfileResponseToFormValues, type UserProfileResponse } from './user-profile-form';
|
||||
|
||||
const UsersPage: React.FC = () => {
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
@@ -36,7 +29,6 @@ const UsersPage: React.FC = () => {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
|
||||
|
||||
const handleOpenProfile = async (record: any) => {
|
||||
setProfileUser(record);
|
||||
try {
|
||||
@@ -79,7 +71,6 @@ const UsersPage: React.FC = () => {
|
||||
setLoading(false);
|
||||
}, [showArchived]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
@@ -162,89 +153,99 @@ const UsersPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{ title: 'ID', dataIndex: 'id', width: 60 },
|
||||
{ title: '用户名', dataIndex: 'username', width: 120 },
|
||||
{ title: '姓名', dataIndex: 'name', width: 120 },
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roles',
|
||||
width: 200,
|
||||
render: (v: any[]) =>
|
||||
v && v.length > 0 ? (
|
||||
v.map((r: any) => (
|
||||
<Tag key={r.id} color="blue">
|
||||
{r.name}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Tag color="default">无角色</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'isActive',
|
||||
width: 80,
|
||||
render: (v: boolean) => <Tag color={v ? 'green' : 'default'}>{v ? '启用' : '禁用'}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '最后登录',
|
||||
dataIndex: 'lastLoginAt',
|
||||
width: 170,
|
||||
render: (v: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-'),
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
width: 170,
|
||||
render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 240,
|
||||
fixed: 'right' as const,
|
||||
render: (_: unknown, record: any) => (
|
||||
<Space>
|
||||
<PermissionButton
|
||||
permission="user:edit"
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<IdcardOutlined />}
|
||||
onClick={() => handleOpenProfile(record)}
|
||||
>
|
||||
档案
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
permission="user:edit"
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleEdit(record)}
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
permission="user:reset-password"
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<KeyOutlined />}
|
||||
onClick={() => handleResetPwd(record)}
|
||||
>
|
||||
重置密码
|
||||
</PermissionButton>
|
||||
{record.isArchived ? (
|
||||
<Popconfirm title="确认恢复?" onConfirm={() => handleArchive(record.id, false)}>
|
||||
<PermissionButton permission="user:edit" type="link" size="small">恢复</PermissionButton>
|
||||
</Popconfirm>
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{ title: 'ID', dataIndex: 'id', width: 60 },
|
||||
{ title: '用户名', dataIndex: 'username', width: 120 },
|
||||
{ title: '姓名', dataIndex: 'name', width: 120 },
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roles',
|
||||
width: 200,
|
||||
render: (v: any[]) =>
|
||||
v && v.length > 0 ? (
|
||||
v.map((r: any) => (
|
||||
<Tag key={r.id} color="blue">
|
||||
{r.name}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Popconfirm title="归档后可恢复,确认归档?" onConfirm={() => handleArchive(record.id, true)}>
|
||||
<PermissionButton permission="user:edit" type="link" size="small">归档</PermissionButton>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
], []);
|
||||
<Tag color="default">无角色</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'isActive',
|
||||
width: 80,
|
||||
render: (v: boolean) => <Tag color={v ? 'green' : 'default'}>{v ? '启用' : '禁用'}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '最后登录',
|
||||
dataIndex: 'lastLoginAt',
|
||||
width: 170,
|
||||
render: (v: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-'),
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
width: 170,
|
||||
render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 240,
|
||||
fixed: 'right' as const,
|
||||
render: (_: unknown, record: any) => (
|
||||
<Space>
|
||||
<PermissionButton
|
||||
permission="user:edit"
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<IdcardOutlined />}
|
||||
onClick={() => handleOpenProfile(record)}
|
||||
>
|
||||
档案
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
permission="user:edit"
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleEdit(record)}
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
permission="user:reset-password"
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<KeyOutlined />}
|
||||
onClick={() => handleResetPwd(record)}
|
||||
>
|
||||
重置密码
|
||||
</PermissionButton>
|
||||
{record.isArchived ? (
|
||||
<Popconfirm title="确认恢复?" onConfirm={() => handleArchive(record.id, false)}>
|
||||
<PermissionButton permission="user:edit" type="link" size="small">
|
||||
恢复
|
||||
</PermissionButton>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<Popconfirm
|
||||
title="归档后可恢复,确认归档?"
|
||||
onConfirm={() => handleArchive(record.id, true)}
|
||||
>
|
||||
<PermissionButton permission="user:edit" type="link" size="small">
|
||||
归档
|
||||
</PermissionButton>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -256,7 +257,8 @@ const UsersPage: React.FC = () => {
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: 8,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<h2 style={{ margin: 0 }}>账号管理</h2>
|
||||
<Space wrap>
|
||||
<PermissionButton
|
||||
@@ -374,10 +376,7 @@ const UsersPage: React.FC = () => {
|
||||
<Form.Item name="qualifications" label="资质">
|
||||
<Input.TextArea placeholder="教师资格证号、学历等" rows={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="subjects"
|
||||
label="任教学科"
|
||||
>
|
||||
<Form.Item name="subjects" label="任教学科">
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="输入学科后回车添加"
|
||||
@@ -396,7 +395,7 @@ const UsersPage: React.FC = () => {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user