fix permissions and teacher attendance workflows

This commit is contained in:
2026-07-10 20:40:52 +08:00
parent 247879f276
commit 8ed1682b90
95 changed files with 4745 additions and 1237 deletions

View File

@@ -1,8 +1,26 @@
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import {
Card, Button, Select, Modal, Form, Input, DatePicker, TimePicker,
Popconfirm, message, Space, Spin, Empty, Tag, Tooltip, Segmented,
Badge, Row, Col, Statistic, Alert,
Card,
Button,
Select,
Modal,
Form,
Input,
DatePicker,
TimePicker,
Popconfirm,
Space,
Spin,
Empty,
Tag,
Tooltip,
Segmented,
Badge,
Row,
Col,
Statistic,
Alert,
Switch,
} from 'antd';
import {
CalendarOutlined,
@@ -11,10 +29,17 @@ import {
DeleteOutlined,
CloudSyncOutlined,
PlusOutlined,
EditOutlined,
} from '@ant-design/icons';
import dayjs, { Dayjs } from 'dayjs';
import api from '../../api';
import PermissionButton from '../../components/PermissionButton';
import { message } from '../../ui/app-message';
import {
buildSchedulePayload,
scheduleToFormValues,
type ScheduleFormValues,
} from './schedule-form';
// ---- Types ----
@@ -50,20 +75,14 @@ interface ClassItem {
code: string;
}
interface UserItem {
interface ClassTeacherOption {
id: number;
username: string;
name: string;
userId: number;
username?: string;
name?: string;
roleType: string;
subject?: string | null;
}
interface ScheduleFormValues {
classId: number;
subject: string;
teacherId?: number;
timeRange: [Dayjs, Dayjs];
dateRange: [Dayjs, Dayjs];
}
/** 排班同步返回结果 */
interface ScheduleSyncResult {
scheduleCount: number;
@@ -90,7 +109,7 @@ const SchedulesPage: React.FC = () => {
// Data
const [classrooms, setClassrooms] = useState<ClassroomItem[]>([]);
const [classes, setClasses] = useState<ClassItem[]>([]);
const [users, setUsers] = useState<UserItem[]>([]);
const [classTeachers, setClassTeachers] = useState<ClassTeacherOption[]>([]);
const [matrix, setMatrix] = useState<Record<number, Record<number, ClassScheduleItem[]>>>({});
const [loading, setLoading] = useState(false);
@@ -100,7 +119,8 @@ const SchedulesPage: React.FC = () => {
// Modal
const [modalOpen, setModalOpen] = useState(false);
const [modalMode, setModalMode] = useState<'create' | 'detail'>('create');
const [modalMode, setModalMode] = useState<'create' | 'edit' | 'detail'>('create');
const [editingSchedule, setEditingSchedule] = useState<ClassScheduleItem | null>(null);
const [selectedCell, setSelectedCell] = useState<{
classroomId: number;
weekDay: number;
@@ -112,15 +132,21 @@ const SchedulesPage: React.FC = () => {
const [syncModalOpen, setSyncModalOpen] = useState(false);
const [syncing, setSyncing] = useState(false);
const [syncStatus, setSyncStatus] = useState<{
activeSchedules: number; mappedClasses: number; totalClasses: number;
activeSchedules: number;
mappedClasses: number;
totalClasses: number;
} | null>(null);
const [syncResult, setSyncResult] = useState<{
scheduleCount: number; shiftCount: number; groupCount: number;
syncedItems: number; skippedNoMapping: number;
scheduleCount: number;
shiftCount: number;
groupCount: number;
syncedItems: number;
skippedNoMapping: number;
groups: Array<{ className: string; groupId: number; itemCount: number }>;
} | null>(null);
const [syncDateFrom, setSyncDateFrom] = useState<Dayjs>(dayjs);
const [syncDays, setSyncDays] = useState(30);
const [attendanceMachineOnly, setAttendanceMachineOnly] = useState(false);
/** 打开同步弹窗时先查询就绪状态 */
const openSyncModal = useCallback(async () => {
@@ -128,7 +154,8 @@ const SchedulesPage: React.FC = () => {
setSyncResult(null);
try {
const res = await api.get<{
success: boolean; data: { activeSchedules: number; mappedClasses: number; totalClasses: number };
success: boolean;
data: { activeSchedules: number; mappedClasses: number; totalClasses: number };
}>('/sync/schedule/status');
setSyncStatus(res.data);
} catch {
@@ -141,11 +168,13 @@ const SchedulesPage: React.FC = () => {
setSyncing(true);
try {
const res = await api.post<{
success: boolean; data: ScheduleSyncResult;
success: boolean;
data: ScheduleSyncResult;
}>('/sync/schedule/sync', null, {
params: {
dateFrom: syncDateFrom.format('YYYY-MM-DD'),
days: syncDays,
attendanceMachineOnly,
},
});
setSyncResult(res.data);
@@ -156,7 +185,7 @@ const SchedulesPage: React.FC = () => {
} finally {
setSyncing(false);
}
}, [syncDateFrom, syncDays]);
}, [syncDateFrom, syncDays, attendanceMachineOnly]);
const [form] = Form.useForm<ScheduleFormValues>();
// Derived week/month info
@@ -201,7 +230,6 @@ const SchedulesPage: React.FC = () => {
return weekEnd.format('YYYY-MM-DD');
}, [viewMode, weekEnd, calendarDays]);
// ---- Data fetching ----
const fetchData = useCallback(async () => {
@@ -244,10 +272,6 @@ const SchedulesPage: React.FC = () => {
fetchData();
}, [fetchData]);
useEffect(() => {
api.get<UserItem[]>('/rbac/users').then(setUsers).catch(() => {});
}, []);
// ---- Filtered classrooms ----
const filteredClassrooms = useMemo(() => {
@@ -304,7 +328,10 @@ const SchedulesPage: React.FC = () => {
setModalOpen(true);
} else {
setSelectedSchedules([]);
setEditingSchedule(null);
setModalMode('create');
form.resetFields();
form.setFieldsValue({ classroomId, weekDay });
setModalOpen(true);
}
};
@@ -334,38 +361,75 @@ const SchedulesPage: React.FC = () => {
setModalOpen(true);
};
// ---- Create schedule ----
const loadClassTeachers = useCallback(async (classId: number) => {
try {
const teachers = await api.get<ClassTeacherOption[]>(
`/class-schedules/classes/${classId}/teachers`,
);
setClassTeachers(teachers);
return teachers;
} catch {
setClassTeachers([]);
return [];
}
}, []);
const applyClassTeacherDefaults = useCallback(
async (classId: number, subject?: string) => {
const teachers = await loadClassTeachers(classId);
const subjectTeachers = teachers.filter((teacher) => teacher.roleType === 'subject_teacher');
const matchedBySubject = subject
? subjectTeachers.filter((teacher) => teacher.subject && teacher.subject === subject)
: [];
const matched = matchedBySubject.length > 0 ? matchedBySubject : subjectTeachers;
if (matched.length === 1) {
form.setFieldValue('teacherId', matched[0].userId);
if (!subject && matched[0].subject) form.setFieldValue('subject', matched[0].subject);
} else {
form.setFieldValue('teacherId', undefined);
}
},
[form, loadClassTeachers],
);
// ---- Create / edit schedule ----
const handleSubmit = async () => {
if (!selectedCell) return;
if (modalMode === 'create' && !selectedCell) return;
if (modalMode === 'edit' && !editingSchedule) return;
try {
const values = await form.validateFields();
const values = (await form.validateFields()) as ScheduleFormValues;
setSubmitting(true);
const payload = buildSchedulePayload(values);
const payload = {
classId: values.classId,
subject: values.subject,
teacherId: values.teacherId,
classroomId: selectedCell.classroomId,
weekDay: selectedCell.weekDay,
startTime: values.timeRange[0].format('HH:mm'),
endTime: values.timeRange[1].format('HH:mm'),
startDate: values.dateRange[0].format('YYYY-MM-DD'),
endDate: values.dateRange[1].format('YYYY-MM-DD'),
};
await api.post('/class-schedules', payload);
message.success('排课创建成功');
if (modalMode === 'edit' && editingSchedule) {
await api.put(`/class-schedules/${editingSchedule.id}`, payload);
message.success('排课更新成功,请重新同步到钉钉排班');
} else {
await api.post('/class-schedules', payload);
message.success('排课创建成功');
}
setModalOpen(false);
setEditingSchedule(null);
fetchData();
} catch (e: unknown) {
const err = e as { message?: string; status?: number };
message.error(err?.message || '创建排课失败');
message.error(err?.message || (modalMode === 'edit' ? '更新排课失败' : '创建排课失败'));
} finally {
setSubmitting(false);
}
};
const openEditSchedule = (schedule: ClassScheduleItem) => {
if (schedule.scheduleType === 'RENTAL') {
message.warning('租赁排课请在租赁订单中修改');
return;
}
setEditingSchedule(schedule);
setModalMode('edit');
form.setFieldsValue(scheduleToFormValues(schedule));
void loadClassTeachers(schedule.classId);
};
// ---- Delete schedule ----
@@ -406,15 +470,6 @@ const SchedulesPage: React.FC = () => {
[classes],
);
const userOptions = useMemo(
() =>
users.map((u) => ({
value: u.id,
label: `${u.name || u.username}${u.name ? ` (${u.username})` : ''}`,
})),
[users],
);
// ---- Render ----
const selectedClassroom = selectedCell
@@ -450,7 +505,12 @@ const SchedulesPage: React.FC = () => {
{ label: '月视图', value: 'month' },
]}
/>
<PermissionButton permission="sync:trigger" type="primary" icon={<CloudSyncOutlined />} onClick={openSyncModal}>
<PermissionButton
permission="sync:trigger"
type="primary"
icon={<CloudSyncOutlined />}
onClick={openSyncModal}
>
</PermissionButton>
{viewMode === 'week' ? (
@@ -467,10 +527,7 @@ const SchedulesPage: React.FC = () => {
({startDateStr} ~ {endDateStr})
</span>
</span>
<Button
icon={<RightOutlined />}
onClick={() => setViewDate(viewDate.add(7, 'day'))}
>
<Button icon={<RightOutlined />} onClick={() => setViewDate(viewDate.add(7, 'day'))}>
</Button>
</>
@@ -524,7 +581,7 @@ const SchedulesPage: React.FC = () => {
<Spin spinning={loading}>
{classrooms.length === 0 ? (
<Empty description="暂无教室数据" />
) : (viewMode === 'week' ? (
) : viewMode === 'week' ? (
<div style={{ overflowX: 'auto' }}>
<table
style={{
@@ -724,10 +781,14 @@ const SchedulesPage: React.FC = () => {
transition: 'background 0.15s',
}}
onMouseEnter={(e) => {
(e.currentTarget as HTMLElement).style.background = isCurrentMonth ? '#f0f5ff' : '#f0f0f0';
(e.currentTarget as HTMLElement).style.background = isCurrentMonth
? '#f0f5ff'
: '#f0f0f0';
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLElement).style.background = isCurrentMonth ? '' : '#fafafa';
(e.currentTarget as HTMLElement).style.background = isCurrentMonth
? ''
: '#fafafa';
}}
>
<div
@@ -756,7 +817,7 @@ const SchedulesPage: React.FC = () => {
</tbody>
</table>
</div>
))}
)}
</Spin>
{/* Modal */}
@@ -764,24 +825,25 @@ const SchedulesPage: React.FC = () => {
title={
modalMode === 'create'
? `新增排课 — ${selectedClassroom?.name || ''} · ${selectedCell ? WEEKDAYS[selectedCell.weekDay - 1] : ''}`
: selectedDate
? `排课详情${selectedDate.format('YYYY-MM-DD')} ${WEEKDAYS[selectedDate.day() === 0 ? 6 : selectedDate.day() - 1]}`
: `排课详情 — ${selectedClassroom?.name || ''} · ${selectedCell ? WEEKDAYS[selectedCell.weekDay - 1] : ''}`
: modalMode === 'edit'
? `编辑排课 — ${editingSchedule?.subject || ''}`
: selectedDate
? `排课详情 — ${selectedDate.format('YYYY-MM-DD')} ${WEEKDAYS[selectedDate.day() === 0 ? 6 : selectedDate.day() - 1]}`
: `排课详情 — ${selectedClassroom?.name || ''} · ${selectedCell ? WEEKDAYS[selectedCell.weekDay - 1] : ''}`
}
open={modalOpen}
onCancel={() => setModalOpen(false)}
onOk={modalMode === 'create' ? handleSubmit : undefined}
onCancel={() => {
setModalOpen(false);
setEditingSchedule(null);
}}
onOk={modalMode !== 'detail' ? handleSubmit : undefined}
confirmLoading={submitting}
okText={modalMode === 'create' ? '创建' : undefined}
footer={
modalMode === 'create'
? undefined // use default ok/cancel
: null // no footer for detail mode
}
okText={modalMode === 'edit' ? '保存' : modalMode === 'create' ? '创建' : undefined}
footer={modalMode === 'detail' ? null : undefined}
width={600}
destroyOnHidden
>
{modalMode === 'create' ? (
{modalMode !== 'detail' ? (
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item
name="classId"
@@ -793,6 +855,33 @@ const SchedulesPage: React.FC = () => {
showSearch
optionFilterProp="label"
options={classOptions}
onChange={(classId: number) => {
form.setFieldValue('teacherId', undefined);
void applyClassTeacherDefaults(classId, form.getFieldValue('subject'));
}}
/>
</Form.Item>
<Form.Item
name="classroomId"
label="教室"
rules={[{ required: true, message: '请选择教室' }]}
>
<Select
placeholder="选择教室"
showSearch
optionFilterProp="label"
options={classroomOptions}
/>
</Form.Item>
<Form.Item
name="weekDay"
label="星期"
rules={[{ required: true, message: '请选择星期' }]}
>
<Select
options={WEEKDAY_NUMBERS.map((value) => ({ value, label: WEEKDAYS[value - 1] }))}
/>
</Form.Item>
@@ -801,30 +890,26 @@ const SchedulesPage: React.FC = () => {
label="科目"
rules={[{ required: true, message: '请输入科目' }]}
>
<Input placeholder="如:数学、语文" />
</Form.Item>
<Form.Item
name="teacherId"
label="教师(可选)"
>
<Select
showSearch
allowClear
placeholder="搜索并选择教师"
optionFilterProp="label"
options={userOptions}
<Input
placeholder="如:数学、语文"
onBlur={(event) => {
const classId = form.getFieldValue('classId');
if (classId) void applyClassTeacherDefaults(classId, event.target.value);
}}
/>
</Form.Item>
<Form.Item label="教室">
<Input value={selectedClassroom?.name || ''} disabled />
</Form.Item>
<Form.Item label="星期">
<Input
value={selectedCell ? WEEKDAYS[selectedCell.weekDay - 1] : ''}
disabled
<Form.Item name="teacherId" label="任课老师">
<Select
showSearch
allowClear
placeholder="先选班级;系统会按科目自动带出任课老师"
optionFilterProp="label"
options={classTeachers.map((teacher) => ({
value: teacher.userId,
label: `${teacher.name || teacher.username || `#${teacher.userId}`}${teacher.subject ? ` · ${teacher.subject}` : ''}`,
}))}
notFoundContent="该班级暂无可选教师,请先在班级详情配置教师"
/>
</Form.Item>
@@ -854,14 +939,28 @@ const SchedulesPage: React.FC = () => {
</Form>
) : (
<div style={{ lineHeight: 2 }}>
<div style={{ marginBottom: 12, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div
style={{
marginBottom: 12,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<span style={{ fontWeight: 500 }}></span>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => {
setEditingSchedule(null);
setModalMode('create');
form.resetFields();
form.setFieldsValue({
classroomId: selectedCell?.classroomId,
weekDay:
selectedCell?.weekDay ?? (selectedDate ? selectedDate.day() || 7 : undefined),
dateRange: selectedDate ? [selectedDate, selectedDate] : undefined,
});
}}
>
@@ -877,7 +976,13 @@ const SchedulesPage: React.FC = () => {
style={{ marginBottom: 8 }}
styles={{ body: { padding: 12 } }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
}}
>
<div>
<div>
<strong></strong>
@@ -890,9 +995,9 @@ const SchedulesPage: React.FC = () => {
{s.teacherId != null && (
<div>
<strong></strong>
{users.find((u) => u.id === s.teacherId)?.name
|| users.find((u) => u.id === s.teacherId)?.username
|| `#${s.teacherId}`}
{classTeachers.find((u) => u.userId === s.teacherId)?.name ||
classTeachers.find((u) => u.userId === s.teacherId)?.username ||
`#${s.teacherId}`}
</div>
)}
<div>
@@ -916,21 +1021,33 @@ const SchedulesPage: React.FC = () => {
<Tag color={s.status === 'active' ? 'green' : 'default'}>{s.status}</Tag>
</div>
</div>
<Popconfirm
title="确认删除该排课?"
onConfirm={() => handleDelete(s.id)}
okText="删除"
cancelText="取消"
>
<PermissionButton
permission="schedule:delete"
size="small"
danger
icon={<DeleteOutlined />}
<Space>
{s.scheduleType !== 'RENTAL' && (
<PermissionButton
permission="schedule:edit"
size="small"
icon={<EditOutlined />}
onClick={() => openEditSchedule(s)}
>
</PermissionButton>
)}
<Popconfirm
title="确认删除该排课?"
onConfirm={() => handleDelete(s.id)}
okText="删除"
cancelText="取消"
>
</PermissionButton>
</Popconfirm>
<PermissionButton
permission="schedule:delete"
size="small"
danger
icon={<DeleteOutlined />}
>
</PermissionButton>
</Popconfirm>
</Space>
</div>
</Card>
))
@@ -943,22 +1060,45 @@ const SchedulesPage: React.FC = () => {
<Modal
title="同步排课到钉钉考勤排班"
open={syncModalOpen}
onCancel={() => { setSyncModalOpen(false); setSyncResult(null); }}
footer={syncResult ? [
<Button key="close" onClick={() => { setSyncModalOpen(false); setSyncResult(null); }}></Button>,
] : [
<Button key="cancel" onClick={() => { setSyncModalOpen(false); setSyncResult(null); }}></Button>,
<Button
key="sync"
type="primary"
icon={<CloudSyncOutlined />}
loading={syncing}
onClick={handleSyncSchedule}
disabled={!syncStatus || syncStatus.activeSchedules === 0}
>
</Button>,
]}
onCancel={() => {
setSyncModalOpen(false);
setSyncResult(null);
}}
footer={
syncResult
? [
<Button
key="close"
onClick={() => {
setSyncModalOpen(false);
setSyncResult(null);
}}
>
</Button>,
]
: [
<Button
key="cancel"
onClick={() => {
setSyncModalOpen(false);
setSyncResult(null);
}}
>
</Button>,
<Button
key="sync"
type="primary"
icon={<CloudSyncOutlined />}
loading={syncing}
onClick={handleSyncSchedule}
disabled={!syncStatus || syncStatus.activeSchedules === 0}
>
</Button>,
]
}
width={560}
>
{syncResult ? (
@@ -1014,11 +1154,18 @@ const SchedulesPage: React.FC = () => {
title="已就绪班级"
value={syncStatus.mappedClasses}
suffix={`/ ${syncStatus.totalClasses}`}
valueStyle={{ color: syncStatus.mappedClasses < syncStatus.totalClasses ? '#faad14' : '#3f8600' }}
valueStyle={{
color:
syncStatus.mappedClasses < syncStatus.totalClasses ? '#faad14' : '#3f8600',
}}
/>
</Col>
<Col span={8}>
<Statistic title="无绑定学生班级" value={syncStatus.totalClasses - syncStatus.mappedClasses} suffix="个" />
<Statistic
title="无绑定学生班级"
value={syncStatus.totalClasses - syncStatus.mappedClasses}
suffix="个"
/>
</Col>
</Row>
{syncStatus.mappedClasses < syncStatus.totalClasses && (
@@ -1031,9 +1178,13 @@ const SchedulesPage: React.FC = () => {
)}
<div style={{ marginBottom: 16 }}>
<div style={{ marginBottom: 8, fontWeight: 500 }}></div>
<Space>
<Space wrap>
<span></span>
<DatePicker value={syncDateFrom} onChange={(d) => d && setSyncDateFrom(d)} allowClear={false} />
<DatePicker
value={syncDateFrom}
onChange={(d) => d && setSyncDateFrom(d)}
allowClear={false}
/>
<span></span>
<Select
value={syncDays}
@@ -1048,9 +1199,32 @@ const SchedulesPage: React.FC = () => {
]}
/>
</Space>
<div style={{ marginTop: 16 }}>
<Space align="start">
<Switch checked={attendanceMachineOnly} onChange={setAttendanceMachineOnly} />
<div>
<div style={{ fontWeight: 500 }}></div>
<div style={{ color: '#8c8c8c', fontSize: 12, marginTop: 2 }}>
Wi-Fi
</div>
</div>
</Space>
</div>
{attendanceMachineOnly && (
<Alert
type="info"
showIcon
message="已存在的同名考勤组也会在本次同步中更新为仅考勤机打卡。"
style={{ marginTop: 12 }}
/>
)}
</div>
{syncStatus.activeSchedules === 0 && (
<Alert type="info" message="当前没有活跃排课。请先在排课页面创建排课记录。" showIcon />
<Alert
type="info"
message="当前没有活跃排课。请先在排课页面创建排课记录。"
showIcon
/>
)}
</div>
) : (

View File

@@ -0,0 +1,52 @@
import { describe, expect, it } from 'vitest';
import dayjs from 'dayjs';
import { buildSchedulePayload, scheduleToFormValues } from './schedule-form';
describe('schedule edit form mapping', () => {
it('fills an existing schedule into editable form values', () => {
const values = scheduleToFormValues({
id: 3,
classId: 1,
classroomId: 1,
weekDay: 5,
subject: '语文',
teacherId: null,
startTime: '14:00',
endTime: '18:00',
startDate: '2026-07-01',
endDate: '2026-07-31',
});
expect(values.classroomId).toBe(1);
expect(values.weekDay).toBe(5);
expect(values.timeRange.map((item) => item.format('HH:mm'))).toEqual(['14:00', '18:00']);
expect(values.dateRange.map((item) => item.format('YYYY-MM-DD'))).toEqual([
'2026-07-01',
'2026-07-31',
]);
});
it('builds the update payload from edited form values', () => {
expect(
buildSchedulePayload({
classId: 1,
classroomId: 2,
weekDay: 6,
subject: '作文',
teacherId: 4,
timeRange: [dayjs('2026-01-01 13:30'), dayjs('2026-01-01 17:20')],
dateRange: [dayjs('2026-08-01'), dayjs('2026-08-31')],
}),
).toEqual({
classId: 1,
classroomId: 2,
weekDay: 6,
subject: '作文',
teacherId: 4,
startTime: '13:30',
endTime: '17:20',
startDate: '2026-08-01',
endDate: '2026-08-31',
});
});
});

View File

@@ -0,0 +1,46 @@
import dayjs, { type Dayjs } from 'dayjs';
export interface ScheduleFormValues {
classId: number;
classroomId: number;
weekDay: number;
subject: string;
teacherId?: number;
timeRange: [Dayjs, Dayjs];
dateRange: [Dayjs, Dayjs];
}
export interface EditableSchedule {
id: number;
classId: number;
classroomId: number;
weekDay: number;
subject: string;
teacherId: number | null;
startTime: string;
endTime: string;
startDate: string;
endDate: string;
}
export const scheduleToFormValues = (schedule: EditableSchedule): ScheduleFormValues => ({
classId: schedule.classId,
classroomId: schedule.classroomId,
weekDay: schedule.weekDay,
subject: schedule.subject,
teacherId: schedule.teacherId ?? undefined,
timeRange: [dayjs(`2000-01-01 ${schedule.startTime}`), dayjs(`2000-01-01 ${schedule.endTime}`)],
dateRange: [dayjs(schedule.startDate), dayjs(schedule.endDate)],
});
export const buildSchedulePayload = (values: ScheduleFormValues) => ({
classId: values.classId,
classroomId: values.classroomId,
weekDay: values.weekDay,
subject: values.subject,
teacherId: values.teacherId,
startTime: values.timeRange[0].format('HH:mm'),
endTime: values.timeRange[1].format('HH:mm'),
startDate: values.dateRange[0].format('YYYY-MM-DD'),
endDate: values.dateRange[1].format('YYYY-MM-DD'),
});