feat: 考勤模块重构与钉钉考勤同步
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useApiMutation } from '../hooks/useApiMutation';
|
||||
import { validateResponse } from '../utils/validate';
|
||||
import { attendanceDevicesSchema, classroomOptionsSchema } from '../api/schemas';
|
||||
import { Empty, Form, Input, Modal, Popconfirm, Select, Space, Table, Tag } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
@@ -30,34 +34,57 @@ const statusMeta = {
|
||||
} as const;
|
||||
|
||||
const AttendanceDevicesPage: React.FC = () => {
|
||||
const [data, setData] = useState<AttendanceDeviceRow[]>([]);
|
||||
const [classrooms, setClassrooms] = useState<ClassroomOption[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<AttendanceDeviceRow | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [devices, classroomList] = await Promise.all([
|
||||
api.get<AttendanceDeviceRow[]>('/attendance-devices'),
|
||||
api.get<ClassroomOption[]>('/classrooms'),
|
||||
]);
|
||||
setData(devices);
|
||||
setClassrooms(classroomList.filter((item: any) => item.status !== 'archived'));
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '加载考勤机绑定失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const {
|
||||
data: fetchResult = { devices: [], classrooms: [] },
|
||||
isLoading,
|
||||
isFetching,
|
||||
} = useQuery<{ devices: AttendanceDeviceRow[]; classrooms: ClassroomOption[] }>({
|
||||
queryKey: ['attendance-devices'],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const [devices, classroomList] = await Promise.all([
|
||||
api.get<AttendanceDeviceRow[]>('/attendance-devices'),
|
||||
api.get<ClassroomOption[]>('/classrooms'),
|
||||
]);
|
||||
return {
|
||||
devices: validateResponse<AttendanceDeviceRow[]>(attendanceDevicesSchema, devices),
|
||||
classrooms: validateResponse<ClassroomOption[]>(
|
||||
classroomOptionsSchema,
|
||||
classroomList,
|
||||
).filter((item: any) => item.status !== 'archived'),
|
||||
};
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '加载考勤机绑定失败');
|
||||
return { devices: [], classrooms: [] };
|
||||
}
|
||||
},
|
||||
});
|
||||
const data = fetchResult.devices;
|
||||
const classrooms = fetchResult.classrooms;
|
||||
const loading = isLoading || isFetching;
|
||||
|
||||
useEffect(() => {
|
||||
void loadData();
|
||||
}, []);
|
||||
const saveMutation = useApiMutation(
|
||||
async (values: Record<string, unknown>) =>
|
||||
editing
|
||||
? api.put(`/attendance-devices/${editing.id}`, values)
|
||||
: api.post('/attendance-devices', values),
|
||||
{ invalidate: [['attendance-devices']] },
|
||||
);
|
||||
const saveCellMutation = useApiMutation(
|
||||
async ({ record, field, value }: { record: AttendanceDeviceRow; field: string; value: unknown }) =>
|
||||
api.put(`/attendance-devices/${record.id}`, { [field]: value }),
|
||||
{ invalidate: [['attendance-devices']] },
|
||||
);
|
||||
const deleteMutation = useApiMutation(
|
||||
async (id: number) => api.delete(`/attendance-devices/${id}`),
|
||||
{ invalidate: [['attendance-devices']] },
|
||||
);
|
||||
|
||||
const classroomOptions = useMemo(
|
||||
() =>
|
||||
@@ -102,37 +129,33 @@ const AttendanceDevicesPage: React.FC = () => {
|
||||
const values = await form.validateFields();
|
||||
setSaving(true);
|
||||
try {
|
||||
if (editing) {
|
||||
await api.put(`/attendance-devices/${editing.id}`, values);
|
||||
message.success('考勤机绑定已更新');
|
||||
} else {
|
||||
await api.post('/attendance-devices', values);
|
||||
message.success('考勤机绑定已创建');
|
||||
}
|
||||
await saveMutation.mutateAsync(values);
|
||||
message.success(editing ? '考勤机绑定已更新' : '考勤机绑定已创建');
|
||||
setModalOpen(false);
|
||||
setEditing(null);
|
||||
form.resetFields();
|
||||
await loadData();
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '保存失败');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const saveCell = async (record: AttendanceDeviceRow, field: string, value: unknown) => {
|
||||
await api.put(`/attendance-devices/${record.id}`, { [field]: value });
|
||||
message.success('已保存');
|
||||
await loadData();
|
||||
try {
|
||||
await saveCellMutation.mutateAsync({ record, field, value });
|
||||
message.success('已保存');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await api.delete(`/attendance-devices/${id}`);
|
||||
await deleteMutation.mutateAsync(id);
|
||||
message.success('已停用绑定');
|
||||
await loadData();
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '停用失败');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user