2026-05-10 22:43:57 +09:00
|
|
|
import { useRef, useState } from 'react';
|
|
|
|
|
import { message } from 'antd';
|
|
|
|
|
import {
|
|
|
|
|
ModalForm, ProForm, ProFormDatePicker, ProFormDigit, ProFormSelect, ProFormText,
|
|
|
|
|
ProTable, type ActionType, type ProColumns,
|
|
|
|
|
} from '@ant-design/pro-components';
|
|
|
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
|
|
import dayjs from 'dayjs';
|
2026-05-09 22:38:10 +09:00
|
|
|
import PageContainer from '@/components/common/PageContainer';
|
|
|
|
|
import CodeBadge from '@/components/common/CodeBadge';
|
|
|
|
|
import MoneyText from '@/components/common/MoneyText';
|
2026-05-10 22:43:57 +09:00
|
|
|
import PermissionButton from '@/components/common/PermissionButton';
|
2026-05-10 22:20:33 +09:00
|
|
|
import { useCommonCodes } from '@/hooks/useCommonCodes';
|
2026-05-10 22:43:57 +09:00
|
|
|
import { contractApi, ContractResp, ContractSaveReq, ContractSearchParam } from '@/api/contract';
|
|
|
|
|
import { productApi } from '@/api/product';
|
|
|
|
|
import { agentApi } from '@/api/agent';
|
2026-05-09 22:38:10 +09:00
|
|
|
|
2026-06-12 23:40:28 +09:00
|
|
|
/**
|
|
|
|
|
* 계약 관리 화면.
|
|
|
|
|
* API: contractApi.list/create, 등록 모달용 productApi.list / agentApi.list
|
|
|
|
|
* 보험종류/계약상태/납입주기/판매채널은 공통코드로 불러와 CodeBadge·CodeSelect 에 사용한다.
|
|
|
|
|
*/
|
2026-05-09 22:38:10 +09:00
|
|
|
export default function ContractList() {
|
2026-05-10 22:43:57 +09:00
|
|
|
const actionRef = useRef<ActionType>();
|
2026-06-12 23:40:28 +09:00
|
|
|
const [open, setOpen] = useState(false); // 계약 등록 모달 열림 여부
|
2026-06-06 17:32:25 +09:00
|
|
|
const { data: insuranceCodes = [] } = useCommonCodes('INSURANCE_TYPE');
|
|
|
|
|
const { data: statusCodes = [] } = useCommonCodes('CONTRACT_STATUS');
|
|
|
|
|
const { data: payCycleCodes = [] } = useCommonCodes('PAY_CYCLE');
|
|
|
|
|
const { data: salesChannelCodes = [] } = useCommonCodes('SALES_CHANNEL');
|
2026-05-10 22:43:57 +09:00
|
|
|
|
|
|
|
|
// 등록 모달용 - 상품/설계사 옵션
|
|
|
|
|
const { data: products } = useQuery({
|
|
|
|
|
queryKey: ['products', 'all'],
|
|
|
|
|
queryFn: () => productApi.list({ pageSize: 999 }),
|
|
|
|
|
enabled: open,
|
|
|
|
|
});
|
|
|
|
|
const { data: agents } = useQuery({
|
|
|
|
|
queryKey: ['agents', 'all'],
|
|
|
|
|
queryFn: () => agentApi.list({ pageSize: 999 }),
|
|
|
|
|
enabled: open,
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-12 23:40:28 +09:00
|
|
|
// 계약 등록 제출 — 날짜를 문자열로 변환 후 생성 API 호출, 성공 시 목록 새로고침
|
2026-05-10 22:43:57 +09:00
|
|
|
const onSubmit = async (v: any) => {
|
|
|
|
|
const req: ContractSaveReq = {
|
|
|
|
|
...v,
|
|
|
|
|
contractDate: v.contractDate ? dayjs(v.contractDate).format('YYYY-MM-DD') : undefined,
|
|
|
|
|
effectiveDate: v.effectiveDate ? dayjs(v.effectiveDate).format('YYYY-MM-DD') : undefined,
|
|
|
|
|
};
|
|
|
|
|
await contractApi.create(req);
|
|
|
|
|
message.success('등록 완료');
|
|
|
|
|
setOpen(false);
|
|
|
|
|
actionRef.current?.reload();
|
|
|
|
|
return true;
|
|
|
|
|
};
|
2026-05-09 22:38:10 +09:00
|
|
|
|
2026-05-10 22:20:33 +09:00
|
|
|
const columns: ProColumns<ContractResp>[] = [
|
2026-05-10 22:43:57 +09:00
|
|
|
{ title: '검색어', dataIndex: 'searchKeyword', hideInTable: true,
|
|
|
|
|
fieldProps: { placeholder: '계약자/증권번호' } },
|
2026-05-10 22:20:33 +09:00
|
|
|
{
|
|
|
|
|
title: '보험종류', dataIndex: 'insuranceType', width: 100, valueType: 'select',
|
|
|
|
|
valueEnum: Object.fromEntries(insuranceCodes.map((c) => [c.code, { text: c.codeName }])),
|
|
|
|
|
render: (_, r) => <CodeBadge groupCode="INSURANCE_TYPE" value={r.insuranceType} />,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '상태', dataIndex: 'status', width: 90, valueType: 'select',
|
|
|
|
|
valueEnum: Object.fromEntries(statusCodes.map((c) => [c.code, { text: c.codeName }])),
|
|
|
|
|
render: (_, r) => <CodeBadge groupCode="CONTRACT_STATUS" value={r.status} />,
|
|
|
|
|
},
|
|
|
|
|
{ title: '증권번호', dataIndex: 'policyNo', width: 140, search: false },
|
|
|
|
|
{ title: '계약자', dataIndex: 'contractorName', width: 100, search: false },
|
|
|
|
|
{ title: '설계사', dataIndex: 'agentName', width: 100, search: false },
|
|
|
|
|
{ title: '보험사', dataIndex: 'companyName', width: 120, search: false },
|
|
|
|
|
{ title: '상품', dataIndex: 'productName', width: 220, search: false },
|
|
|
|
|
{
|
|
|
|
|
title: '보험료', dataIndex: 'premium', width: 120, align: 'right', search: false,
|
|
|
|
|
render: (_, r) => <MoneyText value={r.premium} />,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '납입주기', dataIndex: 'payCycle', width: 90, search: false,
|
|
|
|
|
render: (_, r) => <CodeBadge groupCode="PAY_CYCLE" value={r.payCycle} />,
|
|
|
|
|
},
|
|
|
|
|
{ title: '계약일', dataIndex: 'contractDate', width: 110, search: false },
|
2026-06-06 17:32:25 +09:00
|
|
|
{
|
|
|
|
|
title: '판매채널', dataIndex: 'salesChannel', width: 110, search: false,
|
|
|
|
|
render: (_, r) => r.salesChannelName ?? r.salesChannel ?? '-',
|
|
|
|
|
},
|
2026-05-10 22:20:33 +09:00
|
|
|
];
|
2026-05-09 22:38:10 +09:00
|
|
|
|
|
|
|
|
return (
|
2026-05-10 22:20:33 +09:00
|
|
|
<PageContainer title="계약 관리" description="보험사·상품·계약 통합 관리">
|
|
|
|
|
<ProTable<ContractResp, ContractSearchParam>
|
2026-05-10 22:43:57 +09:00
|
|
|
actionRef={actionRef}
|
2026-05-09 22:38:10 +09:00
|
|
|
rowKey="contractId"
|
2026-05-10 22:20:33 +09:00
|
|
|
columns={columns}
|
|
|
|
|
scroll={{ x: 1300 }}
|
|
|
|
|
request={async (params) => {
|
|
|
|
|
const { current, pageSize, ...rest } = params as any;
|
|
|
|
|
const res = await contractApi.list({ ...rest, pageNum: current, pageSize });
|
|
|
|
|
return { data: res.list, total: res.total, success: true };
|
|
|
|
|
}}
|
2026-05-09 22:38:10 +09:00
|
|
|
pagination={{
|
2026-05-10 22:43:57 +09:00
|
|
|
pageSize: 20, showSizeChanger: true, pageSizeOptions: ['20', '50', '100'],
|
2026-05-10 22:20:33 +09:00
|
|
|
showTotal: (total) => `총 ${total.toLocaleString()}건`,
|
2026-05-09 22:38:10 +09:00
|
|
|
}}
|
2026-05-10 22:20:33 +09:00
|
|
|
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
|
|
|
|
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
2026-05-10 22:43:57 +09:00
|
|
|
toolBarRender={() => [
|
|
|
|
|
<PermissionButton key="create" menuCode="CONTRACT_LIST" permCode="CREATE" type="primary"
|
|
|
|
|
onClick={() => setOpen(true)}>+ 계약 등록</PermissionButton>,
|
|
|
|
|
]}
|
2026-05-10 22:20:33 +09:00
|
|
|
dateFormatter="string"
|
2026-05-09 22:38:10 +09:00
|
|
|
/>
|
2026-05-10 22:43:57 +09:00
|
|
|
|
|
|
|
|
<ModalForm
|
|
|
|
|
title="계약 등록"
|
|
|
|
|
open={open}
|
|
|
|
|
onOpenChange={setOpen}
|
|
|
|
|
width={680}
|
|
|
|
|
layout="vertical"
|
|
|
|
|
onFinish={onSubmit}
|
|
|
|
|
submitter={{ searchConfig: { submitText: '등록', resetText: '취소' } }}
|
|
|
|
|
modalProps={{ destroyOnClose: true, maskClosable: false }}
|
|
|
|
|
>
|
|
|
|
|
<ProForm.Group>
|
|
|
|
|
<ProFormText name="policyNo" label="증권번호" rules={[{ required: true }]} width="md" />
|
|
|
|
|
<ProFormSelect
|
|
|
|
|
name="agentId" label="설계사" rules={[{ required: true }]} width="md" showSearch
|
|
|
|
|
options={agents?.list.map((a) => ({ value: a.agentId, label: `${a.agentName} (${a.licenseNo ?? '-'})` })) ?? []}
|
|
|
|
|
/>
|
|
|
|
|
</ProForm.Group>
|
|
|
|
|
<ProFormSelect
|
|
|
|
|
name="productId" label="상품" rules={[{ required: true }]} showSearch
|
|
|
|
|
options={products?.list.map((p) => ({ value: p.productId, label: `${p.productName} — ${p.companyName}` })) ?? []}
|
|
|
|
|
/>
|
|
|
|
|
<ProForm.Group>
|
|
|
|
|
<ProFormText name="contractorName" label="계약자" width="md" />
|
|
|
|
|
<ProFormText name="insuredName" label="피보험자" width="md" />
|
|
|
|
|
</ProForm.Group>
|
|
|
|
|
<ProForm.Group>
|
|
|
|
|
<ProFormDigit name="premium" label="보험료" width="md" min={0}
|
|
|
|
|
fieldProps={{ formatter: (v) => `${v}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',') }} />
|
|
|
|
|
<ProFormSelect
|
|
|
|
|
name="payCycle" label="납입주기" width="md"
|
|
|
|
|
options={payCycleCodes.map((c) => ({ value: c.code, label: c.codeName }))}
|
|
|
|
|
/>
|
|
|
|
|
</ProForm.Group>
|
|
|
|
|
<ProForm.Group>
|
|
|
|
|
<ProFormDatePicker name="contractDate" label="계약일" width="md" />
|
|
|
|
|
<ProFormDatePicker name="effectiveDate" label="효력발생일" width="md" />
|
|
|
|
|
</ProForm.Group>
|
2026-06-06 17:32:25 +09:00
|
|
|
<ProFormSelect
|
|
|
|
|
name="salesChannel"
|
|
|
|
|
label="판매채널"
|
|
|
|
|
width="md"
|
|
|
|
|
options={salesChannelCodes.map((c) => ({ value: c.code, label: c.codeName }))}
|
|
|
|
|
placeholder="선택 안 함(직판 기본)"
|
|
|
|
|
/>
|
2026-05-10 22:43:57 +09:00
|
|
|
</ModalForm>
|
2026-05-09 22:38:10 +09:00
|
|
|
</PageContainer>
|
|
|
|
|
);
|
|
|
|
|
}
|