import { useRef, useState } from 'react'; import { Modal, Space, message } from 'antd'; import { ModalForm, ProForm, ProFormDigit, ProFormSelect, ProFormText, ProTable, type ActionType, type ProColumns, } from '@ant-design/pro-components'; import dayjs from 'dayjs'; import PageContainer from '@/components/common/PageContainer'; import CodeBadge from '@/components/common/CodeBadge'; import MoneyText from '@/components/common/MoneyText'; import PermissionButton from '@/components/common/PermissionButton'; import { useCommonCodes } from '@/hooks/useCommonCodes'; import { deductionApi, DeductionResp, DeductionSaveReq, DeductionSearchParam, } from '@/api/deduction'; // ── 상수 ───────────────────────────────────────────────────────────────── const MENU_CODE = 'DEDUCTION'; // YYYYMM 유효성 검사 function isValidYYYYMM(v: string): boolean { if (!/^\d{6}$/.test(v)) return false; const year = parseInt(v.slice(0, 4), 10); const month = parseInt(v.slice(4, 6), 10); return year >= 2000 && year <= 2099 && month >= 1 && month <= 12; } // ── 컴포넌트 ────────────────────────────────────────────────────────────── export default function DeductionList() { const actionRef = useRef(); const [modal, setModal] = useState<{ open: boolean; editRecord?: DeductionResp }>({ open: false, }); const { data: statusCodes = [] } = useCommonCodes('DEDUCTION_STATUS'); const { data: typeCodes = [] } = useCommonCodes('DEDUCTION_TYPE'); // ── 열기 ─────────────────────────────────────────────────────────────── const openCreate = () => setModal({ open: true }); const openEdit = (record: DeductionResp) => setModal({ open: true, editRecord: record }); // ── 취소 ─────────────────────────────────────────────────────────────── const onCancel = (record: DeductionResp) => { Modal.confirm({ title: '공제 취소', content: `[${record.agentName}] ${record.settleMonth} 공제를 취소하시겠습니까?`, okType: 'danger', okText: '취소 처리', cancelText: '닫기', onOk: async () => { await deductionApi.cancel(record.deductionId); message.success('취소 완료'); actionRef.current?.reload(); }, }); }; // ── 저장 ─────────────────────────────────────────────────────────────── const onSubmit = async (values: Record) => { const req: DeductionSaveReq = { agentId: values.agentId as number, settleMonth: values.settleMonth as string, deductionType: values.deductionType as string, amount: values.amount as number, description: values.description as string | undefined, }; if (modal.editRecord) { await deductionApi.update(modal.editRecord.deductionId, req); message.success('수정 완료'); } else { await deductionApi.create(req); message.success('등록 완료'); } setModal({ open: false }); actionRef.current?.reload(); return true; }; // ── 컬럼 ─────────────────────────────────────────────────────────────── const columns: ProColumns[] = [ { title: '설계사명', dataIndex: 'agentName', width: 110, fixed: 'left', search: false, }, { title: '설계사ID', dataIndex: 'agentId', width: 90, hideInTable: true, valueType: 'digit', }, { title: '정산월', dataIndex: 'settleMonth', width: 90, align: 'center', }, { title: '차감유형', dataIndex: 'deductionType', width: 120, valueType: 'select', valueEnum: Object.fromEntries( typeCodes.map((c) => [c.code, { text: c.codeName }]), ), render: (_, r) => ( ), }, { title: '금액', dataIndex: 'amount', width: 130, align: 'right', search: false, render: (_, r) => , }, { title: '설명', dataIndex: 'description', width: 200, ellipsis: true, search: false, }, { title: '상태', dataIndex: 'status', width: 100, valueType: 'select', valueEnum: Object.fromEntries( statusCodes.map((c) => [c.code, { text: c.codeName }]), ), render: (_, r) => ( ), }, { title: '등록일', dataIndex: 'createdAt', width: 160, search: false, render: (_, r) => dayjs(r.createdAt).format('YYYY-MM-DD HH:mm'), }, { title: '액션', valueType: 'option', width: 140, fixed: 'right', render: (_, r) => r.status !== 'CANCELLED' ? ( openEdit(r)} > 수정 onCancel(r)} > 취소 ) : null, }, ]; // ── 렌더링 ───────────────────────────────────────────────────────────── return ( actionRef={actionRef} rowKey="deductionId" columns={columns} scroll={{ x: 1100 }} request={async (params) => { const { current, pageSize, agentId, settleMonth, deductionType, status } = params as DeductionSearchParam & { current?: number; pageSize?: number }; const res = await deductionApi.list({ agentId, settleMonth, deductionType, status, pageNum: current, pageSize, }); return { data: res.list, total: res.total, success: true }; }} pagination={{ pageSize: 30, showSizeChanger: true, pageSizeOptions: ['20', '50', '100'], showTotal: (total) => `총 ${total.toLocaleString()}건`, }} search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false, }} options={{ density: false, fullScreen: true, reload: true, setting: true }} toolBarRender={() => [ + 공제 등록 , ]} dateFormatter="string" /> {/* ── 등록/수정 모달 ─────────────────────────────────────────────── */} !o && setModal({ open: false })} width={520} layout="vertical" key={modal.editRecord?.deductionId ?? 'new'} initialValues={ modal.editRecord ? { agentId: modal.editRecord.agentId, settleMonth: modal.editRecord.settleMonth, deductionType: modal.editRecord.deductionType, amount: modal.editRecord.amount, description: modal.editRecord.description, } : undefined } onFinish={onSubmit} submitter={{ searchConfig: { submitText: modal.editRecord ? '수정' : '등록', resetText: '취소', }, }} modalProps={{ destroyOnClose: true, maskClosable: false }} > !v || isValidYYYYMM(v) ? Promise.resolve() : Promise.reject(new Error('YYYYMM 6자리로 입력하세요 (예: 202501)')), }, ]} width="sm" placeholder="예: 202501" fieldProps={{ maxLength: 6 }} /> ({ value: c.code, label: c.codeName }))} /> v === undefined || v === null || v > 0 ? Promise.resolve() : Promise.reject(new Error('금액은 양수여야 합니다')), }, ]} width="md" min={1} fieldProps={{ precision: 0, formatter: (v) => `${v}`.replace(/\B(?=(\d{3})+(?!\d))/g, ','), }} /> ); }