feat(admin): 入住记录新增编辑弹窗与操作列
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// aislop-ignore-file: duplicate-block -- 列渲染结构相似且字段不同,逻辑已组件化
|
||||
import { Button, Popconfirm, Space, Tag } from 'antd';
|
||||
import { InboxOutlined, LogoutOutlined, SwapOutlined } from '@ant-design/icons';
|
||||
import { EditOutlined, InboxOutlined, LogoutOutlined, SwapOutlined } from '@ant-design/icons';
|
||||
import PermissionButton from '../../components/PermissionButton';
|
||||
import { message } from '../../ui/app-message';
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface OccupancyRow {
|
||||
billingStartDate?: string;
|
||||
billingEndDate?: string;
|
||||
checkOutDate?: string | null;
|
||||
checkOutReason?: string;
|
||||
status?: string;
|
||||
student?: { id?: number; name?: string; studentNo?: string } | null;
|
||||
room?: { id?: number; roomNumber?: string; building?: string } | null;
|
||||
@@ -23,10 +24,12 @@ export interface OccupancyColumnContext {
|
||||
readonly: boolean;
|
||||
canPurge: boolean;
|
||||
canDelete: boolean;
|
||||
canEdit: boolean;
|
||||
onPurge: (id: number, name: string) => void;
|
||||
onArchive: (id: number) => Promise<unknown> | unknown;
|
||||
onCheckOut: (record: OccupancyRow) => void;
|
||||
onTransfer: (record: OccupancyRow) => void;
|
||||
onEdit: (record: OccupancyRow) => void;
|
||||
}
|
||||
|
||||
const buildOccupancyDataColumns = () => {
|
||||
@@ -65,7 +68,17 @@ const buildOccupancyDataColumns = () => {
|
||||
};
|
||||
|
||||
const buildOccupancyActionColumn = (ctx: OccupancyColumnContext) => {
|
||||
const { readonly, canPurge, canDelete, onPurge, onArchive, onCheckOut, onTransfer } = ctx;
|
||||
const {
|
||||
readonly,
|
||||
canPurge,
|
||||
canDelete,
|
||||
canEdit,
|
||||
onPurge,
|
||||
onArchive,
|
||||
onCheckOut,
|
||||
onTransfer,
|
||||
onEdit,
|
||||
} = ctx;
|
||||
return {
|
||||
title: '操作',
|
||||
width: 220,
|
||||
@@ -86,6 +99,16 @@ const buildOccupancyActionColumn = (ctx: OccupancyColumnContext) => {
|
||||
</Space>
|
||||
) : !record.checkOutDate ? (
|
||||
<Space>
|
||||
{canEdit ? (
|
||||
<PermissionButton
|
||||
permission="occupancy:edit"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => onEdit(record)}
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
) : null}
|
||||
<PermissionButton
|
||||
permission="occupancy:checkout"
|
||||
size="small"
|
||||
@@ -106,6 +129,16 @@ const buildOccupancyActionColumn = (ctx: OccupancyColumnContext) => {
|
||||
) : (
|
||||
<Space>
|
||||
<Tag>已退宿</Tag>
|
||||
{canEdit ? (
|
||||
<PermissionButton
|
||||
permission="occupancy:edit"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => onEdit(record)}
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
) : null}
|
||||
{canDelete ? (
|
||||
<Popconfirm
|
||||
title="确定归档此记录?"
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import React, { act } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App, Form } from 'antd';
|
||||
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { page } from '@vitest/browser/context';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import { EditOccupancyModal } from './OccupancyModals';
|
||||
import type { OccupancyRow } from './OccupancyColumns';
|
||||
|
||||
let container: HTMLDivElement | null = null;
|
||||
let root: ReturnType<typeof createRoot> | null = null;
|
||||
|
||||
beforeAll(() => {
|
||||
(
|
||||
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }
|
||||
).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (root) await act(async () => root?.unmount());
|
||||
container?.remove();
|
||||
root = null;
|
||||
container = null;
|
||||
});
|
||||
|
||||
const dateNotBefore =
|
||||
(start: string | Dayjs | null | undefined, messageText: string) =>
|
||||
(_: unknown, value?: Dayjs | null) => {
|
||||
if (!value || !start) return Promise.resolve();
|
||||
const startDate = dayjs.isDayjs(start) ? start : dayjs(start);
|
||||
return value.isBefore(startDate, 'day')
|
||||
? Promise.reject(new Error(messageText))
|
||||
: Promise.resolve();
|
||||
};
|
||||
|
||||
function Harness({
|
||||
record,
|
||||
onForm,
|
||||
}: {
|
||||
record: OccupancyRow | null;
|
||||
onForm?: (form: ReturnType<typeof Form.useForm>[0]) => void;
|
||||
}) {
|
||||
const [form] = Form.useForm();
|
||||
React.useEffect(() => {
|
||||
onForm?.(form);
|
||||
}, [form, onForm]);
|
||||
return (
|
||||
<App>
|
||||
<EditOccupancyModal
|
||||
record={record}
|
||||
canEdit
|
||||
saving={false}
|
||||
form={form}
|
||||
dateNotBefore={dateNotBefore}
|
||||
onOk={() => undefined}
|
||||
onCancel={() => undefined}
|
||||
/>
|
||||
</App>
|
||||
);
|
||||
}
|
||||
|
||||
async function mount(record: OccupancyRow | null, onForm?: HarnessProps['onForm']) {
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
await act(async () => {
|
||||
root?.render(<Harness record={record} onForm={onForm} />);
|
||||
});
|
||||
}
|
||||
|
||||
type HarnessProps = React.ComponentProps<typeof Harness>;
|
||||
|
||||
describe('EditOccupancyModal', () => {
|
||||
it('shows only check-in and billing start dates for an active occupancy', async () => {
|
||||
await mount({
|
||||
id: 1,
|
||||
studentId: 1,
|
||||
roomId: 2,
|
||||
checkInDate: '2026-07-10',
|
||||
billingStartDate: '2026-07-10',
|
||||
});
|
||||
|
||||
expect(document.body.textContent).toContain('编辑入住记录');
|
||||
expect(document.body.textContent).toContain('入住日期');
|
||||
expect(document.body.textContent).toContain('计费起始日');
|
||||
expect(document.body.textContent).not.toContain('退宿日期');
|
||||
expect(document.body.textContent).not.toContain('计费截止日');
|
||||
expect(document.body.textContent).not.toContain('退宿原因');
|
||||
|
||||
// 截图快照:在住记录编辑弹窗
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
await page.screenshot();
|
||||
});
|
||||
|
||||
it('shows checkout fields for a checked-out occupancy', async () => {
|
||||
await mount({
|
||||
id: 1,
|
||||
studentId: 1,
|
||||
roomId: 2,
|
||||
checkInDate: '2026-07-10',
|
||||
billingStartDate: '2026-07-10',
|
||||
checkOutDate: '2026-08-01',
|
||||
checkOutReason: '结业',
|
||||
});
|
||||
|
||||
expect(document.body.textContent).toContain('退宿日期');
|
||||
expect(document.body.textContent).toContain('计费截止日');
|
||||
expect(document.body.textContent).toContain('退宿原因');
|
||||
|
||||
// 截图快照:已退宿记录编辑弹窗
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
await page.screenshot();
|
||||
});
|
||||
|
||||
it('requires keeping the check-out date for a checked-out occupancy', async () => {
|
||||
let form: ReturnType<typeof Form.useForm>[0] | undefined;
|
||||
await mount(
|
||||
{
|
||||
id: 1,
|
||||
studentId: 1,
|
||||
roomId: 2,
|
||||
checkInDate: '2026-07-10',
|
||||
billingStartDate: '2026-07-10',
|
||||
checkOutDate: '2026-08-01',
|
||||
},
|
||||
(f) => {
|
||||
form = f;
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => form?.setFieldsValue({ checkOutDate: undefined }));
|
||||
await expect(form?.validateFields()).rejects.toMatchObject({
|
||||
errorFields: expect.arrayContaining([
|
||||
expect.objectContaining({ name: ['checkOutDate'] }),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a billing start date before the check-in date', async () => {
|
||||
let form: ReturnType<typeof Form.useForm>[0] | undefined;
|
||||
await mount(
|
||||
{
|
||||
id: 1,
|
||||
studentId: 1,
|
||||
roomId: 2,
|
||||
checkInDate: '2026-07-10',
|
||||
billingStartDate: '2026-07-10',
|
||||
},
|
||||
(f) => {
|
||||
form = f;
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () =>
|
||||
form?.setFieldsValue({
|
||||
checkInDate: dayjs('2026-07-12'),
|
||||
billingStartDate: dayjs('2026-07-11'),
|
||||
}),
|
||||
);
|
||||
await expect(form?.validateFields()).rejects.toMatchObject({
|
||||
errorFields: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
errors: ['计费起始日不能早于入住日期'],
|
||||
}),
|
||||
]),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Switch,
|
||||
Tag,
|
||||
} from 'antd';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import { maskIdNumber, maskPhone } from '../../utils/sensitive';
|
||||
import { useDirtyGuard } from '../../hooks/useDirtyGuard';
|
||||
import type { OccupancyRow } from './OccupancyColumns';
|
||||
@@ -574,3 +574,107 @@ export const TransferModal: React.FC<{
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const EditOccupancyModal: React.FC<{
|
||||
record: OccupancyRow | null;
|
||||
canEdit: boolean;
|
||||
saving: boolean;
|
||||
form: ReturnType<typeof Form.useForm>[0];
|
||||
dateNotBefore: (start: string | Dayjs | null | undefined, messageText: string) => unknown;
|
||||
onOk: () => void;
|
||||
onCancel: () => void;
|
||||
}> = ({ record, canEdit, saving, form, dateNotBefore, onOk, onCancel }) => {
|
||||
const editGuard = useDirtyGuard(form);
|
||||
const checkedOut = !!record?.checkOutDate;
|
||||
// 父组件打开编辑弹窗前会用 record 回填表单,这里记录「未修改」基准
|
||||
useEffect(() => {
|
||||
if (record && canEdit) editGuard.snapshot();
|
||||
}, [record, canEdit, editGuard]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`编辑入住记录${record?.student?.name ? ` - ${record.student.name}` : ''}`}
|
||||
open={!!record && canEdit}
|
||||
onOk={canEdit ? onOk : undefined}
|
||||
onCancel={() => editGuard.confirmClose(onCancel)}
|
||||
okText="保存修改"
|
||||
confirmLoading={saving}
|
||||
width={500}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<DateFormItem name="checkInDate" label="入住日期" placeholder="选择入住日期" required />
|
||||
<DateFormItem
|
||||
name="billingStartDate"
|
||||
label="计费起始日"
|
||||
placeholder="选择计费起始日"
|
||||
required
|
||||
dependencies={['checkInDate']}
|
||||
extra="默认与入住日期相同,可调整(如学生要求从次日开始计费)"
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator: dateNotBefore(
|
||||
getFieldValue('checkInDate'),
|
||||
'计费起始日不能早于入住日期',
|
||||
) as never,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
{checkedOut ? (
|
||||
<>
|
||||
<DateFormItem
|
||||
name="checkOutDate"
|
||||
label="退宿日期"
|
||||
placeholder="选择退宿日期"
|
||||
required
|
||||
dependencies={['checkInDate']}
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator: dateNotBefore(
|
||||
getFieldValue('checkInDate'),
|
||||
'退宿日期不能早于入住日期',
|
||||
) as never,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
<DateFormItem
|
||||
name="billingEndDate"
|
||||
label="计费截止日"
|
||||
placeholder="选择计费截止日"
|
||||
dependencies={['billingStartDate', 'checkOutDate']}
|
||||
extra="默认与退宿日期相同"
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator: dateNotBefore(
|
||||
getFieldValue('billingStartDate') || record?.billingStartDate,
|
||||
'计费截止日不能早于计费起始日',
|
||||
) as never,
|
||||
}),
|
||||
({ getFieldValue }) => ({
|
||||
validator: (_: unknown, value?: Dayjs | null) => {
|
||||
const checkOut = getFieldValue('checkOutDate') || record?.checkOutDate;
|
||||
if (!value || !checkOut) return Promise.resolve();
|
||||
return value.isAfter(dayjs(checkOut), 'day')
|
||||
? Promise.reject(new Error('计费截止日不能晚于退宿日期'))
|
||||
: Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
<Form.Item name="checkOutReason" label="退宿原因">
|
||||
<Select
|
||||
allowClear
|
||||
options={[
|
||||
{ value: '换房', label: '换房' },
|
||||
{ value: '退训', label: '退训' },
|
||||
{ value: '结业', label: '结业' },
|
||||
{ value: '毕业', label: '毕业' },
|
||||
{ value: '其他', label: '其他' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
) : null}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,13 +5,14 @@ import { Alert, App, Form } from 'antd';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import api from '../../api';
|
||||
import { message } from '../../ui/app-message';
|
||||
import { buildCheckInPayload, buildTransferPayload } from './occupancy-form';
|
||||
import { buildCheckInPayload, buildEditPayload, buildTransferPayload } from './occupancy-form';
|
||||
import { buildOccupancyColumns } from './OccupancyColumns';
|
||||
import type { OccupancyRow } from './OccupancyColumns';
|
||||
import {
|
||||
BatchCheckOutModal,
|
||||
CheckInModal,
|
||||
CheckOutModal,
|
||||
EditOccupancyModal,
|
||||
TransferModal,
|
||||
} from './OccupancyModals';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
@@ -52,12 +53,14 @@ const OccupanciesPage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { hasPermission, permissionsReady } = usePermission();
|
||||
const canCheckIn = permissionsReady && hasPermission('occupancy:checkin');
|
||||
const canEdit = permissionsReady && hasPermission('occupancy:edit');
|
||||
const canCheckOut = permissionsReady && hasPermission('occupancy:checkout');
|
||||
const canTransfer = permissionsReady && hasPermission('occupancy:transfer');
|
||||
const canDelete = permissionsReady && hasPermission('occupancy:delete');
|
||||
const canPurge = permissionsReady && hasPermission('occupancy:purge');
|
||||
const [checkInModal, setCheckInModal] = useState(false);
|
||||
const [checkOutModal, setCheckOutModal] = useState<any>(null);
|
||||
const [editModal, setEditModal] = useState<any>(null);
|
||||
const [transferModal, setTransferModal] = useState<any>(null);
|
||||
// 入住成功后的「下一步」引导提示
|
||||
const [nextStepHint, setNextStepHint] = useState<'billing' | null>(null);
|
||||
@@ -125,6 +128,7 @@ const OccupanciesPage: React.FC = () => {
|
||||
const {
|
||||
checkInMutation,
|
||||
checkOutMutation,
|
||||
updateMutation,
|
||||
transferMutation,
|
||||
batchCheckOutMutation,
|
||||
batchDeleteMutation,
|
||||
@@ -138,6 +142,7 @@ const OccupanciesPage: React.FC = () => {
|
||||
const [batchLoading, setBatchLoading] = useState(false);
|
||||
const [checkInForm] = Form.useForm();
|
||||
const [checkOutForm] = Form.useForm();
|
||||
const [editForm] = Form.useForm();
|
||||
const [transferForm] = Form.useForm();
|
||||
const [batchCheckOutForm] = Form.useForm();
|
||||
const [availableBeds, setAvailableBeds] = useState<any[]>([]);
|
||||
@@ -307,6 +312,24 @@ const OccupanciesPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = async () => {
|
||||
const values = await editForm.validateFields();
|
||||
setSaving(true);
|
||||
try {
|
||||
await updateMutation.mutateAsync({
|
||||
id: editModal.id,
|
||||
payload: buildEditPayload(values),
|
||||
});
|
||||
message.success('编辑成功');
|
||||
setEditModal(null);
|
||||
editForm.resetFields();
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTransfer = async () => {
|
||||
const values = await transferForm.validateFields();
|
||||
setSaving(true);
|
||||
@@ -418,6 +441,7 @@ const OccupanciesPage: React.FC = () => {
|
||||
readonly: viewPolicy.readonly,
|
||||
canPurge,
|
||||
canDelete,
|
||||
canEdit,
|
||||
onPurge: handlePurge,
|
||||
onArchive: (id) => archiveMutation.mutateAsync(id),
|
||||
onCheckOut: (record) => {
|
||||
@@ -431,14 +455,26 @@ const OccupanciesPage: React.FC = () => {
|
||||
setTransferModal(record);
|
||||
transferForm.setFieldsValue({ transferDate: dayjs() });
|
||||
},
|
||||
onEdit: (record) => {
|
||||
editForm.setFieldsValue({
|
||||
checkInDate: dayjs(record.checkInDate),
|
||||
billingStartDate: dayjs(record.billingStartDate || record.checkInDate),
|
||||
checkOutDate: record.checkOutDate ? dayjs(record.checkOutDate) : undefined,
|
||||
billingEndDate: record.billingEndDate ? dayjs(record.billingEndDate) : undefined,
|
||||
checkOutReason: record.checkOutReason || undefined,
|
||||
});
|
||||
setEditModal(record);
|
||||
},
|
||||
}),
|
||||
[
|
||||
viewPolicy.readonly,
|
||||
canPurge,
|
||||
canDelete,
|
||||
canEdit,
|
||||
handlePurge,
|
||||
archiveMutation,
|
||||
checkOutForm,
|
||||
editForm,
|
||||
transferForm,
|
||||
],
|
||||
);
|
||||
@@ -602,6 +638,15 @@ const OccupanciesPage: React.FC = () => {
|
||||
onOk={handleCheckOut}
|
||||
onCancel={() => setCheckOutModal(null)}
|
||||
/>
|
||||
<EditOccupancyModal
|
||||
record={editModal}
|
||||
canEdit={canEdit}
|
||||
saving={saving}
|
||||
form={editForm}
|
||||
dateNotBefore={dateNotBefore}
|
||||
onOk={handleEdit}
|
||||
onCancel={() => setEditModal(null)}
|
||||
/>
|
||||
<BatchCheckOutModal
|
||||
open={batchCheckOutModal}
|
||||
canCheckOut={canCheckOut}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import dayjs from 'dayjs';
|
||||
import { buildCheckInPayload, buildTransferPayload } from './occupancy-form';
|
||||
import { buildCheckInPayload, buildEditPayload, buildTransferPayload } from './occupancy-form';
|
||||
|
||||
describe('occupancy check-in form', () => {
|
||||
it('submits all required manual check-in fields with defaults', () => {
|
||||
@@ -73,3 +73,51 @@ describe('occupancy transfer form', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('occupancy edit form', () => {
|
||||
it('submits only the two date fields for an active occupancy', () => {
|
||||
expect(
|
||||
buildEditPayload({
|
||||
checkInDate: dayjs('2026-07-18'),
|
||||
billingStartDate: dayjs('2026-07-18'),
|
||||
}),
|
||||
).toEqual({
|
||||
checkInDate: '2026-07-18',
|
||||
billingStartDate: '2026-07-18',
|
||||
});
|
||||
});
|
||||
|
||||
it('submits checkout fields for a checked-out occupancy and trims the reason', () => {
|
||||
expect(
|
||||
buildEditPayload({
|
||||
checkInDate: dayjs('2026-07-18'),
|
||||
billingStartDate: dayjs('2026-07-18'),
|
||||
checkOutDate: dayjs('2026-08-01'),
|
||||
billingEndDate: dayjs('2026-07-31'),
|
||||
checkOutReason: ' 结业 ',
|
||||
}),
|
||||
).toEqual({
|
||||
checkInDate: '2026-07-18',
|
||||
billingStartDate: '2026-07-18',
|
||||
checkOutDate: '2026-08-01',
|
||||
billingEndDate: '2026-07-31',
|
||||
checkOutReason: '结业',
|
||||
});
|
||||
});
|
||||
|
||||
it('leaves optional checkout fields undefined when cleared', () => {
|
||||
expect(
|
||||
buildEditPayload({
|
||||
checkInDate: dayjs('2026-07-18'),
|
||||
billingStartDate: dayjs('2026-07-18'),
|
||||
checkOutDate: dayjs('2026-08-01'),
|
||||
}),
|
||||
).toEqual({
|
||||
checkInDate: '2026-07-18',
|
||||
billingStartDate: '2026-07-18',
|
||||
checkOutDate: '2026-08-01',
|
||||
billingEndDate: undefined,
|
||||
checkOutReason: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,3 +48,28 @@ export const buildTransferPayload = (values: TransferFormValues) => ({
|
||||
newBillingStartDate: values.newBillingStartDate?.format('YYYY-MM-DD'),
|
||||
reason: values.reason,
|
||||
});
|
||||
|
||||
export interface EditFormValues {
|
||||
checkInDate: Dayjs;
|
||||
billingStartDate: Dayjs;
|
||||
checkOutDate?: Dayjs;
|
||||
billingEndDate?: Dayjs;
|
||||
checkOutReason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑入住记录:在住记录只提交入住日期/计费起始日;
|
||||
* 已退宿记录额外附带退宿日期、计费截止日与退宿原因(reason 去空格)。
|
||||
*/
|
||||
export const buildEditPayload = (values: EditFormValues) => {
|
||||
const payload: Record<string, string | undefined> = {
|
||||
checkInDate: values.checkInDate.format('YYYY-MM-DD'),
|
||||
billingStartDate: values.billingStartDate.format('YYYY-MM-DD'),
|
||||
};
|
||||
if (values.checkOutDate) {
|
||||
payload.checkOutDate = values.checkOutDate.format('YYYY-MM-DD');
|
||||
payload.billingEndDate = values.billingEndDate?.format('YYYY-MM-DD');
|
||||
payload.checkOutReason = values.checkOutReason?.trim() || undefined;
|
||||
}
|
||||
return payload;
|
||||
};
|
||||
|
||||
@@ -12,6 +12,11 @@ export function useOccupancyMutations() {
|
||||
api.put(`/occupancies/${id}/check-out`, payload),
|
||||
{ invalidate: invalidateOccupancies },
|
||||
);
|
||||
const updateMutation = useApiMutation(
|
||||
async ({ id, payload }: { id: number; payload: Record<string, unknown> }) =>
|
||||
api.put(`/occupancies/${id}`, payload),
|
||||
{ invalidate: invalidateOccupancies },
|
||||
);
|
||||
const transferMutation = useApiMutation(
|
||||
async ({ id, payload }: { id: number; payload: unknown }) =>
|
||||
api.put(`/occupancies/${id}/transfer`, payload),
|
||||
@@ -51,6 +56,7 @@ export function useOccupancyMutations() {
|
||||
return {
|
||||
checkInMutation,
|
||||
checkOutMutation,
|
||||
updateMutation,
|
||||
transferMutation,
|
||||
batchCheckOutMutation,
|
||||
batchDeleteMutation,
|
||||
|
||||
Reference in New Issue
Block a user