Files
gongxue-base/apps/admin/src/pages/ClassroomRentals/RentalTable.tsx

341 lines
9.6 KiB
TypeScript

import React from 'react';
import {
Button,
Empty,
Popconfirm,
Space,
Table,
Tag,
Tooltip,
Upload,
} from 'antd';
import {
CheckOutlined,
FileTextOutlined,
StopOutlined,
UploadOutlined,
} from '@ant-design/icons';
import dayjs from 'dayjs';
import PermissionButton from '../../components/PermissionButton';
import EditableCell from '../../components/EditableCell';
import { message } from '../../ui/app-message';
const RENTAL_FIELDS = {
classroomId: 'classroomId',
lesseeOrganizationId: 'lesseeOrganizationId',
startDate: 'startDate',
endDate: 'endDate',
dailyRate: 'dailyRate',
totalAmount: 'totalAmount',
} as const;
export interface RentalTableProps {
data: any[];
loading: boolean;
classrooms: any[];
organizations: any[];
canPurgeRental: boolean;
hasPermission: (permission: string) => boolean;
onSaveCell: (record: any, field: string, value: unknown) => Promise<void> | void;
onEdit: (record: any) => void;
onAction: (id: number, action: 'cancel' | 'end') => void;
onArchive: (id: number) => void;
onPurge: (id: number, name: string) => void;
onDownloadContract: (id: number, filename?: string) => void;
onDeleteContract: (id: number) => void;
onUploadContract: (id: number, formData: FormData) => Promise<unknown>;
}
export const RentalTable: React.FC<RentalTableProps> = ({
data,
loading,
classrooms,
organizations,
canPurgeRental,
hasPermission,
onSaveCell,
onEdit,
onAction,
onArchive,
onPurge,
onDownloadContract,
onDeleteContract,
onUploadContract,
}) => {
const EditableRentalCell = <R extends { id: number; effectiveStatus?: string }>({
value,
field,
record,
editor,
min,
required,
options,
children,
}: {
value: unknown;
field: string;
record: R;
editor?: React.ComponentProps<typeof EditableCell>['editor'];
min?: number;
required?: boolean;
options?: Array<{ value: string | number; label: string }>;
children?: React.ReactNode;
}) => (
<EditableCell
value={value}
editor={editor}
min={min}
required={required}
options={options}
permission="rental:edit"
disabled={record.effectiveStatus !== 'active'}
onSave={async (next) => {
await onSaveCell(record, field, next);
}}
>
{children ?? String(value ?? '-')}
</EditableCell>
);
const columns = [
{
title: '教室',
width: 120,
dataIndex: 'classroom',
render: (c: any, r: any) => (
<EditableRentalCell
value={r.classroomId}
field={RENTAL_FIELDS.classroomId}
record={r}
editor="select"
options={classrooms
.filter((item) => item.status !== 'archived')
.map((item) => ({
value: item.id,
label: item.building ? `${item.building} · ${item.name}` : item.name,
}))}
required
>
{c ? (
<span>
{c.building ? `${c.building} · ` : ''}
{c.name}
</span>
) : (
'-'
)}
</EditableRentalCell>
),
},
{
title: '承租机构',
width: 100,
dataIndex: 'lesseeOrganization',
render: (t: any, r: any) => (
<EditableRentalCell
value={r.lesseeOrganizationId}
field={RENTAL_FIELDS.lesseeOrganizationId}
record={r}
editor="select"
options={organizations
.filter((item) => item.status !== 'archived')
.map((item) => ({ value: item.id, label: item.name }))}
required
>
{t ? (
<Tag
color={t.color}
style={{ background: t.color, color: '#fff', borderColor: t.color }}
>
{t.name}
</Tag>
) : (
'-'
)}
</EditableRentalCell>
),
},
{
title: '开始日期',
dataIndex: 'startDate',
width: 110,
render: (v: string, r: any) => (
<EditableRentalCell value={v} field={RENTAL_FIELDS.startDate} record={r} editor="date" required>
{v}
</EditableRentalCell>
),
},
{
title: '结束日期',
dataIndex: 'endDate',
width: 110,
render: (v: string, r: any) => (
<EditableRentalCell value={v} field={RENTAL_FIELDS.endDate} record={r} editor="date" required>
{v}
</EditableRentalCell>
),
},
{
title: '时长',
width: 80,
render: (_: any, r: any) => {
const d = dayjs(r.endDate).diff(dayjs(r.startDate), 'day') + 1;
return `${d}`;
},
},
{
title: '日租金',
dataIndex: 'dailyRate',
width: 100,
render: (v: any, r: any) => (
<EditableRentalCell value={v} field={RENTAL_FIELDS.dailyRate} record={r} editor="money" min={0.01}>
{v ? `¥${v}` : '-'}
</EditableRentalCell>
),
},
{
title: '总额',
dataIndex: 'totalAmount',
width: 100,
render: (v: any, r: any) => (
<EditableRentalCell value={v} field={RENTAL_FIELDS.totalAmount} record={r} editor="money" min={0.01}>
{v ? `¥${v}` : '-'}
</EditableRentalCell>
),
},
{
title: '状态',
dataIndex: 'effectiveStatus',
width: 90,
render: (status: string) => {
const config: Record<string, { text: string; color: string }> = {
active: { text: '进行中', color: 'green' },
ended: { text: '已结束', color: 'default' },
cancelled: { text: '已取消', color: 'red' },
};
return <Tag color={config[status]?.color}>{config[status]?.text || status}</Tag>;
},
},
{
title: '合同',
width: 120,
dataIndex: 'contractPath',
render: (v: string, r: any) =>
v ? (
<Space>
<Tooltip title={r.contractOriginalName}>
<Button
size="small"
icon={<FileTextOutlined />}
onClick={() => onDownloadContract(r.id, r.contractOriginalName)}
>
</Button>
</Tooltip>
{hasPermission('rental:edit') ? (
<Popconfirm title="移除合同文件?" onConfirm={() => onDeleteContract(r.id)}>
<Button size="small" danger icon={<StopOutlined />} aria-label="移除合同文件" />
</Popconfirm>
) : null}
</Space>
) : hasPermission('rental:edit') ? (
<Upload
accept="application/pdf"
showUploadList={false}
customRequest={async ({ file, onSuccess, onError }: any) => {
if (file.size > 10 * 1024 * 1024) {
message.error('文件不能超过 10MB');
onError?.(new Error('size'));
return;
}
const formData = new FormData();
formData.append('file', file);
try {
await onUploadContract(r.id, formData);
message.success('合同已上传');
onSuccess?.({});
} catch (e) {
onError?.(e as Error);
}
}}
>
<Button size="small" icon={<UploadOutlined />}>
PDF
</Button>
</Upload>
) : (
'-'
),
},
{
title: '操作',
width: 150,
render: (_: any, record: any) => (
<Space>
{record.effectiveStatus === 'active' && (
<>
<PermissionButton permission="rental:edit" size="small" onClick={() => onEdit(record)}>
</PermissionButton>
<Popconfirm title="确定取消该租赁?" onConfirm={() => onAction(record.id, 'cancel')}>
<PermissionButton
permission="rental:edit"
size="small"
danger
icon={<StopOutlined />}
>
</PermissionButton>
</Popconfirm>
{!dayjs(record.startDate).isAfter(dayjs(), 'day') && (
<Popconfirm title="确定今天结束该租赁?" onConfirm={() => onAction(record.id, 'end')}>
<PermissionButton permission="rental:edit" size="small" icon={<CheckOutlined />}>
</PermissionButton>
</Popconfirm>
)}
</>
)}
{record.effectiveStatus !== 'active' && (
<Popconfirm
title="确定归档该租赁订单?合同文件会保留。"
onConfirm={() => onArchive(record.id)}
>
<PermissionButton permission="rental:delete" size="small" danger>
</PermissionButton>
</Popconfirm>
)}
{record.status === 'cancelled' && canPurgeRental ? (
<Button
size="small"
danger
type="link"
onClick={() => onPurge(record.id, record.lesseeOrganization?.name || `订单${record.id}`)}
>
</Button>
) : null}
</Space>
),
},
];
return (
<Table
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
locale={{ emptyText: <Empty description="暂无数据" /> }}
pagination={{
defaultPageSize: 15,
showSizeChanger: true,
pageSizeOptions: [15, 30, 50, 100],
showTotal: (total) => `${total}`,
}}
scroll={{ x: 1200 }}
/>
);
};