|
|
@@ -1,13 +1,19 @@
|
|
|
import React, { useState } from 'react';
|
|
|
-import { Table, Button, Space, Input, Modal, Form, Select, DatePicker, InputNumber } from 'antd';
|
|
|
+import { Table, Button, Space, Input, Modal, Form, Select, DatePicker, InputNumber, Popconfirm } from 'antd';
|
|
|
import ClientSelect from '@/client/admin/components/ClientSelect';
|
|
|
import { App } from 'antd';
|
|
|
-import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, FileTextOutlined } from '@ant-design/icons';
|
|
|
+import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, FileTextOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
import { hetongClient } from '@/client/api';
|
|
|
import type { InferResponseType } from 'hono/client';
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
+// 合同状态枚举
|
|
|
+const CONTRACT_STATUS = {
|
|
|
+ PENDING: '待审',
|
|
|
+ ACTIVE: '合同有效'
|
|
|
+} as const;
|
|
|
+
|
|
|
// 定义类型
|
|
|
type HetongItem = InferResponseType<typeof hetongClient.$get, 200>['data'][0];
|
|
|
type HetongListResponse = InferResponseType<typeof hetongClient.$get, 200>;
|
|
|
@@ -115,6 +121,28 @@ const Contracts: React.FC = () => {
|
|
|
message.error('操作失败,请重试');
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
+ // 审核合同
|
|
|
+ const auditContract = useMutation({
|
|
|
+ mutationFn: async (id: number) => {
|
|
|
+ const response = await hetongClient[':id'].$put({
|
|
|
+ param: { id },
|
|
|
+ json: {
|
|
|
+ status: '合同有效',
|
|
|
+ auditTime: new Date().toISOString()
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (!response.ok) throw new Error('Failed to audit contract');
|
|
|
+ return response.json();
|
|
|
+ },
|
|
|
+ onSuccess: () => {
|
|
|
+ message.success('合同审核成功');
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['contracts'] });
|
|
|
+ },
|
|
|
+ onError: () => {
|
|
|
+ message.error('审核失败,请重试');
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
// 删除合同记录
|
|
|
const deleteContract = useMutation({
|
|
|
@@ -156,59 +184,131 @@ const Contracts: React.FC = () => {
|
|
|
// 表格列定义
|
|
|
const columns = [
|
|
|
{
|
|
|
- title: '合同ID',
|
|
|
+ title: '编号',
|
|
|
dataIndex: 'id',
|
|
|
key: 'id',
|
|
|
+ width: 80,
|
|
|
+ align: 'center' as const,
|
|
|
},
|
|
|
{
|
|
|
- title: '合同编号',
|
|
|
+ title: '客户名称',
|
|
|
+ dataIndex: ['client', 'companyName'],
|
|
|
+ key: 'clientName',
|
|
|
+ ellipsis: true,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '合同名称',
|
|
|
dataIndex: 'contractNumber',
|
|
|
- key: 'contractNumber',
|
|
|
+ key: 'contractName',
|
|
|
+ ellipsis: true,
|
|
|
},
|
|
|
{
|
|
|
- title: '客户',
|
|
|
- dataIndex: ['client','companyName'],
|
|
|
- key: 'clientId',
|
|
|
+ title: '起始时间',
|
|
|
+ dataIndex: 'startDate',
|
|
|
+ key: 'startDate',
|
|
|
+ width: 120,
|
|
|
+ render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-',
|
|
|
},
|
|
|
{
|
|
|
- title: '合同类型',
|
|
|
- dataIndex: 'type',
|
|
|
- key: 'type',
|
|
|
+ title: '预计签约时间',
|
|
|
+ dataIndex: 'contractDate',
|
|
|
+ key: 'contractDate',
|
|
|
+ width: 120,
|
|
|
+ render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-',
|
|
|
},
|
|
|
{
|
|
|
- title: '合同金额',
|
|
|
+ title: '总金额',
|
|
|
dataIndex: 'amount',
|
|
|
key: 'amount',
|
|
|
- render: (amount: number, record: HetongItem) =>
|
|
|
- `${record.currency || 'CNY'} ${Number(amount).toFixed(2)}`,
|
|
|
+ width: 120,
|
|
|
+ render: (amount: number, record: HetongItem) =>
|
|
|
+ `${record.currency || 'CNY'} ${Number(amount).toLocaleString()}`,
|
|
|
+ align: 'right' as const,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '已收款',
|
|
|
+ dataIndex: 'receivedAmount',
|
|
|
+ key: 'receivedAmount',
|
|
|
+ width: 100,
|
|
|
+ render: (received: number, record: HetongItem) =>
|
|
|
+ `${record.currency || 'CNY'} ${Number(received || 0).toLocaleString()}`,
|
|
|
+ align: 'right' as const,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '欠款',
|
|
|
+ key: 'debt',
|
|
|
+ width: 100,
|
|
|
+ render: (_: any, record: HetongItem) => {
|
|
|
+ const debt = Number(record.amount || 0) - Number(record.receivedAmount || 0);
|
|
|
+ return `${record.currency || 'CNY'} ${debt.toLocaleString()}`;
|
|
|
+ },
|
|
|
+ align: 'right' as const,
|
|
|
},
|
|
|
{
|
|
|
- title: '状态',
|
|
|
+ title: '合同状态',
|
|
|
dataIndex: 'status',
|
|
|
key: 'status',
|
|
|
+ width: 100,
|
|
|
+ render: (status: string) => (
|
|
|
+ <span style={{
|
|
|
+ color: status === '合同有效' ? 'green' : 'orange',
|
|
|
+ fontWeight: 'bold'
|
|
|
+ }}>
|
|
|
+ {status}
|
|
|
+ </span>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '审核时间',
|
|
|
+ dataIndex: 'auditTime',
|
|
|
+ key: 'auditTime',
|
|
|
+ width: 120,
|
|
|
+ render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-',
|
|
|
},
|
|
|
{
|
|
|
- title: '有效期',
|
|
|
- key: 'dateRange',
|
|
|
- render: (_: any, record: HetongItem) =>
|
|
|
- `${record.startDate ? new Date(record.startDate).toLocaleDateString() : '-'} ~ ${record.endDate ? new Date(record.endDate).toLocaleDateString() : '-'}`
|
|
|
+ title: '录入时间',
|
|
|
+ dataIndex: 'createdAt',
|
|
|
+ key: 'createdAt',
|
|
|
+ width: 120,
|
|
|
+ render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-',
|
|
|
},
|
|
|
{
|
|
|
- title: '操作',
|
|
|
+ title: '管理',
|
|
|
key: 'action',
|
|
|
+ width: 180,
|
|
|
+ fixed: 'right' as const,
|
|
|
render: (_: any, record: HetongItem) => (
|
|
|
- <Space size="middle">
|
|
|
- <Button
|
|
|
- type="text"
|
|
|
- icon={<EditOutlined />}
|
|
|
+ <Space size="small">
|
|
|
+ {record.status === '待审' && (
|
|
|
+ <Popconfirm
|
|
|
+ title="确认审核此合同?"
|
|
|
+ description={`将合同"${record.contractNumber}"状态设为"合同有效"`}
|
|
|
+ onConfirm={() => auditContract.mutate(record.id)}
|
|
|
+ okText="确认"
|
|
|
+ cancelText="取消"
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ icon={<CheckCircleOutlined />}
|
|
|
+ >
|
|
|
+ 审核
|
|
|
+ </Button>
|
|
|
+ </Popconfirm>
|
|
|
+ )}
|
|
|
+ <Button
|
|
|
+ type="text"
|
|
|
+ size="small"
|
|
|
+ icon={<EditOutlined />}
|
|
|
onClick={() => showModal(record)}
|
|
|
>
|
|
|
编辑
|
|
|
</Button>
|
|
|
- <Button
|
|
|
- type="text"
|
|
|
- danger
|
|
|
- icon={<DeleteOutlined />}
|
|
|
+ <Button
|
|
|
+ type="text"
|
|
|
+ danger
|
|
|
+ size="small"
|
|
|
+ icon={<DeleteOutlined />}
|
|
|
onClick={() => deleteContract.mutate(record.id.toString())}
|
|
|
>
|
|
|
删除
|
|
|
@@ -362,9 +462,15 @@ const Contracts: React.FC = () => {
|
|
|
<Form.Item
|
|
|
name="status"
|
|
|
label="合同状态"
|
|
|
- rules={[{ required: true, message: '请输入合同状态' }]}
|
|
|
+ rules={[{ required: true, message: '请选择合同状态' }]}
|
|
|
>
|
|
|
- <Input placeholder="请输入合同状态:如生效中、已结束等" />
|
|
|
+ <Select
|
|
|
+ placeholder="请选择合同状态"
|
|
|
+ options={[
|
|
|
+ { label: '待审', value: '待审' },
|
|
|
+ { label: '合同有效', value: '合同有效' }
|
|
|
+ ]}
|
|
|
+ />
|
|
|
</Form.Item>
|
|
|
</div>
|
|
|
|