동기화
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
import { useState } from 'react';
|
||||
import { Alert, Button, Form, Input, InputNumber, Modal, Space, Tabs, message } from 'antd';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { ColDef } from '@ag-grid-community/core';
|
||||
import dayjs from 'dayjs';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import SearchForm from '@/components/common/SearchForm';
|
||||
import DataGrid from '@/components/common/DataGrid';
|
||||
import PermissionButton from '@/components/common/PermissionButton';
|
||||
import {
|
||||
commissionApi,
|
||||
CollectionRuleRow, CollectionLedgerRow,
|
||||
CommissionSearchParam,
|
||||
} from '@/api/commission';
|
||||
import { GRAY, RADIUS, SHADOW } from '@/theme/tokens';
|
||||
|
||||
const fmt = (v: unknown) => (v as number)?.toLocaleString() ?? '-';
|
||||
|
||||
const RULE_COLS: ColDef<CollectionRuleRow>[] = [
|
||||
{ field: 'carrierName', headerName: '보험사', width: 140 },
|
||||
{ field: 'productType', headerName: '상품유형', width: 120 },
|
||||
{ field: 'paymentCycle', headerName: '납입주기', width: 100 },
|
||||
{ field: 'ratePercent', headerName: '수수료율(%)', width: 120, type: 'numericColumn' },
|
||||
{ field: 'effectiveFrom', headerName: '적용시작', width: 120 },
|
||||
{ field: 'effectiveTo', headerName: '적용종료', width: 120 },
|
||||
{ field: 'status', headerName: '상태', width: 90 },
|
||||
];
|
||||
|
||||
const LEDGER_COLS: ColDef<CollectionLedgerRow>[] = [
|
||||
{ field: 'settleMonth', headerName: '정산월', width: 110, pinned: 'left' },
|
||||
{ field: 'agentName', headerName: '설계사', width: 120 },
|
||||
{ field: 'contractNo', headerName: '계약번호', width: 140 },
|
||||
{ field: 'collectionAmount', headerName: '수납금액', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'ratePercent', headerName: '수수료율(%)', width: 110, type: 'numericColumn' },
|
||||
{ field: 'commissionAmount', headerName: '수수료', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'status', headerName: '상태', width: 90 },
|
||||
];
|
||||
|
||||
export default function CollectionCommission() {
|
||||
const qc = useQueryClient();
|
||||
const [ledgerParams, setLedgerParams] = useState<CommissionSearchParam>({
|
||||
settleMonth: dayjs().format('YYYYMM'), pageSize: 200,
|
||||
});
|
||||
const [ruleModalOpen, setRuleModalOpen] = useState(false);
|
||||
const [editingRule, setEditingRule] = useState<CollectionRuleRow | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { data: rulesData, isLoading: rulesLoading, isError: rulesError } = useQuery({
|
||||
queryKey: ['collection', 'rules'],
|
||||
queryFn: () => commissionApi.collectionRules(),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const { data: ledgersData, isLoading: ledgersLoading } = useQuery({
|
||||
queryKey: ['collection', 'ledgers', ledgerParams],
|
||||
queryFn: () => commissionApi.collectionLedgers(ledgerParams),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const rules = rulesData?.list ?? [];
|
||||
const ledgers = ledgersData?.list ?? [];
|
||||
|
||||
const openCreate = () => { setEditingRule(null); form.resetFields(); setRuleModalOpen(true); };
|
||||
const openEdit = (row: CollectionRuleRow) => { setEditingRule(row); form.setFieldsValue(row); setRuleModalOpen(true); };
|
||||
|
||||
const handleSave = async () => {
|
||||
const values = await form.validateFields();
|
||||
try {
|
||||
if (editingRule) {
|
||||
await commissionApi.collectionRuleUpdate(editingRule.ruleId, values);
|
||||
message.success('수정 완료');
|
||||
} else {
|
||||
await commissionApi.collectionRuleCreate(values);
|
||||
message.success('등록 완료');
|
||||
}
|
||||
qc.invalidateQueries({ queryKey: ['collection', 'rules'] });
|
||||
setRuleModalOpen(false);
|
||||
} catch { /* request.ts 처리 */ }
|
||||
};
|
||||
|
||||
const handleDelete = (id: number) => Modal.confirm({
|
||||
title: '룰 삭제', content: '삭제하시겠습니까?',
|
||||
onOk: async () => {
|
||||
await commissionApi.collectionRuleDelete(id);
|
||||
message.success('삭제 완료');
|
||||
qc.invalidateQueries({ queryKey: ['collection', 'rules'] });
|
||||
},
|
||||
});
|
||||
|
||||
const pinnedLedger = ledgers.length > 0 ? {
|
||||
agentName: '합계',
|
||||
collectionAmount: ledgers.reduce((s, r) => s + (r.collectionAmount ?? 0), 0),
|
||||
commissionAmount: ledgers.reduce((s, r) => s + (r.commissionAmount ?? 0), 0),
|
||||
} as Partial<CollectionLedgerRow> : undefined;
|
||||
|
||||
const cardStyle = { background: '#fff', borderRadius: RADIUS.lg, border: `1px solid ${GRAY[100]}`, boxShadow: SHADOW.sm };
|
||||
|
||||
return (
|
||||
<PageContainer title="수금수수료" description="수금수수료 룰 관리 및 원장 조회">
|
||||
{rulesError && (
|
||||
<Alert type="warning" showIcon message="API 미응답" description="/api/collection-commissions 를 확인하세요." style={{ marginBottom: 16 }} />
|
||||
)}
|
||||
|
||||
<Tabs
|
||||
defaultActiveKey="rules"
|
||||
items={[
|
||||
{
|
||||
key: 'rules',
|
||||
label: '수수료 룰',
|
||||
children: (
|
||||
<ProCard style={cardStyle}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<PermissionButton menuCode="COLLECT_COMM" permCode="CREATE" type="primary" onClick={openCreate}>
|
||||
+ 룰 등록
|
||||
</PermissionButton>
|
||||
</div>
|
||||
<DataGrid<CollectionRuleRow>
|
||||
rows={rules}
|
||||
columns={[
|
||||
...RULE_COLS,
|
||||
{
|
||||
headerName: '액션', width: 160, pinned: 'right',
|
||||
cellRenderer: (p: { data: CollectionRuleRow }) => (
|
||||
<Space>
|
||||
<PermissionButton menuCode="COLLECT_COMM" permCode="UPDATE" size="small" onClick={() => openEdit(p.data)}>수정</PermissionButton>
|
||||
<PermissionButton menuCode="COLLECT_COMM" permCode="DELETE" size="small" danger onClick={() => handleDelete(p.data.ruleId)}>삭제</PermissionButton>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
loading={rulesLoading}
|
||||
height={520}
|
||||
rowKey="ruleId"
|
||||
/>
|
||||
</ProCard>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'ledgers',
|
||||
label: '수금 원장',
|
||||
children: (
|
||||
<>
|
||||
<SearchForm
|
||||
conditions={[
|
||||
{ type: 'text', name: 'agentName', label: '설계사명', span: 6 },
|
||||
{ type: 'month', name: 'settleMonth', label: '정산월', span: 6 },
|
||||
]}
|
||||
onSearch={(v) => setLedgerParams({ ...v as CommissionSearchParam, pageSize: 200 })}
|
||||
onReset={() => setLedgerParams({ settleMonth: dayjs().format('YYYYMM'), pageSize: 200 })}
|
||||
/>
|
||||
<ProCard style={cardStyle}>
|
||||
<DataGrid<CollectionLedgerRow>
|
||||
rows={ledgers}
|
||||
columns={LEDGER_COLS}
|
||||
loading={ledgersLoading}
|
||||
height={520}
|
||||
rowKey="ledgerId"
|
||||
pinnedBottomRow={pinnedLedger}
|
||||
/>
|
||||
</ProCard>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={editingRule ? '룰 수정' : '룰 등록'}
|
||||
open={ruleModalOpen}
|
||||
onOk={handleSave}
|
||||
onCancel={() => setRuleModalOpen(false)}
|
||||
okText="저장"
|
||||
cancelText="취소"
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="carrierName" label="보험사명" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="productType" label="상품유형" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="paymentCycle" label="납입주기">
|
||||
<Input placeholder="MONTHLY / ANNUAL" />
|
||||
</Form.Item>
|
||||
<Form.Item name="ratePercent" label="수수료율(%)" rules={[{ required: true }]}>
|
||||
<InputNumber style={{ width: '100%' }} min={0} max={100} step={0.01} />
|
||||
</Form.Item>
|
||||
<Form.Item name="effectiveFrom" label="적용시작" rules={[{ required: true }]}>
|
||||
<Input placeholder="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
<Form.Item name="effectiveTo" label="적용종료">
|
||||
<Input placeholder="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { useState } from 'react';
|
||||
import { Alert } from 'antd';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { ColDef } from '@ag-grid-community/core';
|
||||
import dayjs from 'dayjs';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import SearchForm from '@/components/common/SearchForm';
|
||||
import DataGrid from '@/components/common/DataGrid';
|
||||
import { commissionApi, CommissionMasterRow, CommissionSearchParam } from '@/api/commission';
|
||||
import { GRAY, RADIUS, SHADOW } from '@/theme/tokens';
|
||||
|
||||
const fmt = (v: unknown) => (v as number)?.toLocaleString() ?? '-';
|
||||
|
||||
const COLUMNS: ColDef<CommissionMasterRow>[] = [
|
||||
{ field: 'settleMonth', headerName: '정산월', width: 110, pinned: 'left' },
|
||||
{ field: 'agentName', headerName: '설계사', width: 120, pinned: 'left' },
|
||||
{ field: 'commissionTypeName', headerName: '수수료 종류', width: 140 },
|
||||
{ field: 'grossAmount', headerName: '총수수료', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'netAmount', headerName: '실지급', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'status', headerName: '상태', width: 100 },
|
||||
];
|
||||
|
||||
export default function CommissionMaster() {
|
||||
const [params, setParams] = useState<CommissionSearchParam>({
|
||||
settleMonth: dayjs().format('YYYYMM'),
|
||||
pageSize: 200,
|
||||
});
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: ['commission', 'master', params],
|
||||
queryFn: () => commissionApi.masterList(params),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const rows = data?.list ?? [];
|
||||
|
||||
const pinnedRow = rows.length > 0 ? {
|
||||
agentName: '합계',
|
||||
commissionTypeName: '',
|
||||
grossAmount: rows.reduce((s, r) => s + (r.grossAmount ?? 0), 0),
|
||||
netAmount: rows.reduce((s, r) => s + (r.netAmount ?? 0), 0),
|
||||
} as Partial<CommissionMasterRow> : undefined;
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="수수료 통합 마스터"
|
||||
description="설계사+정산월 기준 9종 수수료 통합 조회"
|
||||
>
|
||||
<SearchForm
|
||||
conditions={[
|
||||
{ type: 'text', name: 'agentName', label: '설계사명', span: 6 },
|
||||
{ type: 'month', name: 'settleMonth', label: '정산월', span: 6 },
|
||||
{ type: 'code', name: 'commissionType', label: '수수료 종류', groupCode: 'COMMISSION_TYPE', span: 6 },
|
||||
]}
|
||||
onSearch={(v) => setParams({ ...v as CommissionSearchParam, pageSize: 200 })}
|
||||
onReset={() => setParams({ settleMonth: dayjs().format('YYYYMM'), pageSize: 200 })}
|
||||
/>
|
||||
|
||||
{isError && (
|
||||
<Alert type="warning" showIcon
|
||||
message="API 미응답"
|
||||
description="/api/commissions 를 확인하세요."
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ProCard style={{ background: '#fff', borderRadius: RADIUS.lg, border: `1px solid ${GRAY[100]}`, boxShadow: SHADOW.sm }}>
|
||||
<DataGrid<CommissionMasterRow>
|
||||
rows={rows}
|
||||
columns={COLUMNS}
|
||||
loading={isLoading}
|
||||
height={600}
|
||||
rowKey="masterId"
|
||||
pinnedBottomRow={pinnedRow}
|
||||
/>
|
||||
</ProCard>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { useState } from 'react';
|
||||
import { Alert, Radio, Space, Typography } from 'antd';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { ColDef } from '@ag-grid-community/core';
|
||||
import dayjs from 'dayjs';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import SearchForm from '@/components/common/SearchForm';
|
||||
import DataGrid from '@/components/common/DataGrid';
|
||||
import { commissionApi, OverrideLedgerRow, OverrideLedgerSearchParam } from '@/api/commission';
|
||||
import { GRAY, RADIUS, SHADOW } from '@/theme/tokens';
|
||||
|
||||
const { Text } = Typography;
|
||||
const fmt = (v: unknown) => (v as number)?.toLocaleString() ?? '-';
|
||||
const fmtPct = (v: unknown) => `${((v as number) ?? 0).toFixed(2)}%`;
|
||||
|
||||
const COLUMNS: ColDef<OverrideLedgerRow>[] = [
|
||||
{ field: 'settleMonth', headerName: '정산월', width: 110, pinned: 'left' },
|
||||
{ field: 'fromAgentName', headerName: '원천 설계사(From)', width: 150 },
|
||||
{ field: 'toAgentName', headerName: '수령 설계사(To)', width: 150 },
|
||||
{ field: 'orgName', headerName: '조직', width: 130 },
|
||||
{ field: 'baseCommission', headerName: '기준 수수료', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'overrideRate', headerName: '오버라이드율', width: 130, type: 'numericColumn', valueFormatter: (p) => fmtPct(p.value) },
|
||||
{ field: 'overrideAmount', headerName: '오버라이드액', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'status', headerName: '상태', width: 90 },
|
||||
];
|
||||
|
||||
type FilterMode = 'both' | 'from' | 'to';
|
||||
|
||||
export default function OverrideLedger() {
|
||||
const [mode, setMode] = useState<FilterMode>('both');
|
||||
const [params, setParams] = useState<OverrideLedgerSearchParam>({
|
||||
settleMonth: dayjs().format('YYYYMM'),
|
||||
pageSize: 200,
|
||||
});
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: ['override', 'ledgers', params, mode],
|
||||
queryFn: () => commissionApi.overrideLedgers(params),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const allRows = data?.list ?? [];
|
||||
|
||||
// 클라이언트사이드 방향 필터
|
||||
const rows = mode === 'from'
|
||||
? allRows.filter((r) => r.fromAgentId === params.fromAgentId)
|
||||
: mode === 'to'
|
||||
? allRows.filter((r) => r.toAgentId === params.toAgentId)
|
||||
: allRows;
|
||||
|
||||
const pinnedRow = rows.length > 0 ? {
|
||||
fromAgentName: '합계',
|
||||
baseCommission: rows.reduce((s, r) => s + (r.baseCommission ?? 0), 0),
|
||||
overrideAmount: rows.reduce((s, r) => s + (r.overrideAmount ?? 0), 0),
|
||||
} as Partial<OverrideLedgerRow> : undefined;
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="오버라이드 원장"
|
||||
description="From-Agent → To-Agent 오버라이드 발생 원장 (조회 전용)"
|
||||
extra={
|
||||
<Space>
|
||||
<Text style={{ fontSize: 13, color: GRAY[500] }}>방향 필터:</Text>
|
||||
<Radio.Group
|
||||
value={mode}
|
||||
onChange={(e) => setMode(e.target.value)}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
size="small"
|
||||
>
|
||||
<Radio.Button value="both">전체</Radio.Button>
|
||||
<Radio.Button value="from">From 기준</Radio.Button>
|
||||
<Radio.Button value="to">To 기준</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{isError && (
|
||||
<Alert type="warning" showIcon message="API 미응답" description="/api/override-ledgers 를 확인하세요." style={{ marginBottom: 16 }} />
|
||||
)}
|
||||
|
||||
<SearchForm
|
||||
conditions={[
|
||||
{ type: 'text', name: 'agentName', label: '설계사명', span: 6 },
|
||||
{ type: 'month', name: 'settleMonth', label: '정산월', span: 6 },
|
||||
]}
|
||||
onSearch={(v) => setParams({ ...v as OverrideLedgerSearchParam, pageSize: 200 })}
|
||||
onReset={() => setParams({ settleMonth: dayjs().format('YYYYMM'), pageSize: 200 })}
|
||||
/>
|
||||
|
||||
<ProCard style={{ background: '#fff', borderRadius: RADIUS.lg, border: `1px solid ${GRAY[100]}`, boxShadow: SHADOW.sm }}>
|
||||
<DataGrid<OverrideLedgerRow>
|
||||
rows={rows}
|
||||
columns={COLUMNS}
|
||||
loading={isLoading}
|
||||
height={600}
|
||||
rowKey="ledgerId"
|
||||
pinnedBottomRow={pinnedRow}
|
||||
/>
|
||||
</ProCard>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
import { useState } from 'react';
|
||||
import { Alert, Col, Form, Input, InputNumber, Modal, Row, Space, Tabs, Tag, Typography, message } from 'antd';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { ColDef } from '@ag-grid-community/core';
|
||||
import dayjs from 'dayjs';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import SearchForm from '@/components/common/SearchForm';
|
||||
import DataGrid from '@/components/common/DataGrid';
|
||||
import PermissionButton from '@/components/common/PermissionButton';
|
||||
import {
|
||||
commissionApi,
|
||||
PersistencyRuleRow, PersistencyBonusRow,
|
||||
CommissionSearchParam,
|
||||
} from '@/api/commission';
|
||||
import { GRAY, RADIUS, SHADOW, COLOR_PRIMARY, COLOR_SUCCESS, COLOR_WARNING } from '@/theme/tokens';
|
||||
|
||||
const { Text } = Typography;
|
||||
const fmt = (v: unknown) => (v as number)?.toLocaleString() ?? '-';
|
||||
const fmtPct = (v: unknown) => `${((v as number) ?? 0).toFixed(1)}%`;
|
||||
|
||||
// 유지보수수당 등급별 색상
|
||||
const BONUS_TYPE_COLOR: Record<string, string> = {
|
||||
'13M': COLOR_PRIMARY,
|
||||
'25M': COLOR_SUCCESS,
|
||||
'37M': COLOR_WARNING,
|
||||
'49M': '#7C3AED',
|
||||
};
|
||||
|
||||
const RULE_COLS: ColDef<PersistencyRuleRow>[] = [
|
||||
{
|
||||
field: 'bonusType', headerName: '유지 기준', width: 100,
|
||||
cellRenderer: (p: { value: string }) => (
|
||||
<Tag style={{ background: BONUS_TYPE_COLOR[p.value] ?? GRAY[200], color: '#fff', border: 'none', borderRadius: 4 }}>
|
||||
{p.value}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{ field: 'minRate', headerName: '최저유지율(%)', width: 130, type: 'numericColumn', valueFormatter: (p) => fmtPct(p.value) },
|
||||
{ field: 'bonusAmount', headerName: '정액지급', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'bonusRate', headerName: '정률지급(%)', flex: 1, type: 'numericColumn', valueFormatter: (p) => p.value != null ? fmtPct(p.value) : '-' },
|
||||
{ field: 'effectiveFrom', headerName: '적용시작', width: 120 },
|
||||
{ field: 'effectiveTo', headerName: '적용종료', width: 120 },
|
||||
{ field: 'status', headerName: '상태', width: 90 },
|
||||
];
|
||||
|
||||
const BONUS_COLS: ColDef<PersistencyBonusRow>[] = [
|
||||
{ field: 'evaluationMonth', headerName: '평가월', width: 110, pinned: 'left' },
|
||||
{ field: 'agentName', headerName: '설계사', width: 120 },
|
||||
{
|
||||
field: 'bonusType', headerName: '기준', width: 90,
|
||||
cellRenderer: (p: { value: string }) => (
|
||||
<Tag style={{ background: BONUS_TYPE_COLOR[p.value] ?? GRAY[200], color: '#fff', border: 'none', borderRadius: 4 }}>
|
||||
{p.value}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{ field: 'retentionRate', headerName: '유지율(%)', width: 110, type: 'numericColumn', valueFormatter: (p) => fmtPct(p.value) },
|
||||
{ field: 'bonusAmount', headerName: '지급액', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'paidYn', headerName: '지급여부', width: 90 },
|
||||
{ field: 'status', headerName: '상태', width: 90 },
|
||||
];
|
||||
|
||||
export default function PersistencyBonus() {
|
||||
const qc = useQueryClient();
|
||||
const [bonusParams, setBonusParams] = useState<CommissionSearchParam>({
|
||||
settleMonth: dayjs().format('YYYYMM'), pageSize: 200,
|
||||
});
|
||||
const [ruleModalOpen, setRuleModalOpen] = useState(false);
|
||||
const [editingRule, setEditingRule] = useState<PersistencyRuleRow | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { data: rulesData, isLoading: rulesLoading, isError } = useQuery({
|
||||
queryKey: ['persistency', 'rules'],
|
||||
queryFn: () => commissionApi.persistencyRules(),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const { data: bonusData, isLoading: bonusLoading } = useQuery({
|
||||
queryKey: ['persistency', 'bonuses', bonusParams],
|
||||
queryFn: () => commissionApi.persistencyBonuses(bonusParams),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const rules = rulesData?.list ?? [];
|
||||
const bonuses = bonusData?.list ?? [];
|
||||
|
||||
// 등급별 통계 카드
|
||||
const byType = ['13M', '25M', '37M', '49M'].map((t) => ({
|
||||
type: t,
|
||||
count: bonuses.filter((b) => b.bonusType === t).length,
|
||||
total: bonuses.filter((b) => b.bonusType === t).reduce((s, b) => s + (b.bonusAmount ?? 0), 0),
|
||||
}));
|
||||
|
||||
const openCreate = () => { setEditingRule(null); form.resetFields(); setRuleModalOpen(true); };
|
||||
const openEdit = (row: PersistencyRuleRow) => { setEditingRule(row); form.setFieldsValue(row); setRuleModalOpen(true); };
|
||||
|
||||
const handleSave = async () => {
|
||||
const values = await form.validateFields();
|
||||
try {
|
||||
if (editingRule) {
|
||||
await commissionApi.persistencyRuleUpdate(editingRule.ruleId, values);
|
||||
} else {
|
||||
await commissionApi.persistencyRuleCreate(values);
|
||||
}
|
||||
message.success('저장 완료');
|
||||
qc.invalidateQueries({ queryKey: ['persistency', 'rules'] });
|
||||
setRuleModalOpen(false);
|
||||
} catch { /* handled */ }
|
||||
};
|
||||
|
||||
const handleDelete = (id: number) => Modal.confirm({
|
||||
title: '룰 삭제', onOk: async () => {
|
||||
await commissionApi.persistencyRuleDelete(id);
|
||||
message.success('삭제 완료');
|
||||
qc.invalidateQueries({ queryKey: ['persistency', 'rules'] });
|
||||
},
|
||||
});
|
||||
|
||||
const pinnedBonus = bonuses.length > 0 ? {
|
||||
agentName: '합계',
|
||||
bonusAmount: bonuses.reduce((s, b) => s + (b.bonusAmount ?? 0), 0),
|
||||
} as Partial<PersistencyBonusRow> : undefined;
|
||||
|
||||
const cardStyle = { background: '#fff', borderRadius: RADIUS.lg, border: `1px solid ${GRAY[100]}`, boxShadow: SHADOW.sm };
|
||||
|
||||
return (
|
||||
<PageContainer title="유지보수수당" description="13M/25M/37M/49M 유지율 기반 보너스 관리">
|
||||
{isError && (
|
||||
<Alert type="warning" showIcon message="API 미응답" description="/api/persistency-bonus 를 확인하세요." style={{ marginBottom: 16 }} />
|
||||
)}
|
||||
|
||||
<Tabs
|
||||
defaultActiveKey="rules"
|
||||
items={[
|
||||
{
|
||||
key: 'rules',
|
||||
label: '지급 룰',
|
||||
children: (
|
||||
<ProCard style={cardStyle}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<PermissionButton menuCode="PERSIST_BONUS" permCode="CREATE" type="primary" onClick={openCreate}>
|
||||
+ 룰 등록
|
||||
</PermissionButton>
|
||||
</div>
|
||||
<DataGrid<PersistencyRuleRow>
|
||||
rows={rules}
|
||||
columns={[
|
||||
...RULE_COLS,
|
||||
{
|
||||
headerName: '액션', width: 160, pinned: 'right',
|
||||
cellRenderer: (p: { data: PersistencyRuleRow }) => (
|
||||
<Space>
|
||||
<PermissionButton menuCode="PERSIST_BONUS" permCode="UPDATE" size="small" onClick={() => openEdit(p.data)}>수정</PermissionButton>
|
||||
<PermissionButton menuCode="PERSIST_BONUS" permCode="DELETE" size="small" danger onClick={() => handleDelete(p.data.ruleId)}>삭제</PermissionButton>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
loading={rulesLoading}
|
||||
height={420}
|
||||
rowKey="ruleId"
|
||||
/>
|
||||
</ProCard>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'bonuses',
|
||||
label: '지급 이력',
|
||||
children: (
|
||||
<>
|
||||
<SearchForm
|
||||
conditions={[
|
||||
{ type: 'text', name: 'agentName', label: '설계사명', span: 6 },
|
||||
{ type: 'month', name: 'settleMonth', label: '평가월', span: 6 },
|
||||
]}
|
||||
onSearch={(v) => setBonusParams({ ...v as CommissionSearchParam, pageSize: 200 })}
|
||||
onReset={() => setBonusParams({ settleMonth: dayjs().format('YYYYMM'), pageSize: 200 })}
|
||||
/>
|
||||
|
||||
{/* 등급별 통계 카드 */}
|
||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||
{byType.map((t) => (
|
||||
<Col span={6} key={t.type}>
|
||||
<div style={{
|
||||
...cardStyle, padding: '16px 20px',
|
||||
borderLeft: `4px solid ${BONUS_TYPE_COLOR[t.type] ?? GRAY[300]}`,
|
||||
}}>
|
||||
<Text style={{ fontSize: 13, color: GRAY[500] }}>{t.type} 유지</Text>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, color: BONUS_TYPE_COLOR[t.type] ?? GRAY[700] }}>
|
||||
{t.count}건
|
||||
</div>
|
||||
<Text style={{ fontSize: 12, color: GRAY[400] }}>{t.total.toLocaleString()}원</Text>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<ProCard style={cardStyle}>
|
||||
<DataGrid<PersistencyBonusRow>
|
||||
rows={bonuses}
|
||||
columns={BONUS_COLS}
|
||||
loading={bonusLoading}
|
||||
height={480}
|
||||
rowKey="bonusId"
|
||||
pinnedBottomRow={pinnedBonus}
|
||||
/>
|
||||
</ProCard>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={editingRule ? '룰 수정' : '룰 등록'}
|
||||
open={ruleModalOpen}
|
||||
onOk={handleSave}
|
||||
onCancel={() => setRuleModalOpen(false)}
|
||||
okText="저장" cancelText="취소"
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="bonusType" label="유지 기준" rules={[{ required: true }]}>
|
||||
<Input placeholder="13M / 25M / 37M / 49M" />
|
||||
</Form.Item>
|
||||
<Form.Item name="minRate" label="최저유지율(%)" rules={[{ required: true }]}>
|
||||
<InputNumber style={{ width: '100%' }} min={0} max={100} step={0.1} />
|
||||
</Form.Item>
|
||||
<Form.Item name="bonusAmount" label="정액지급(원)">
|
||||
<InputNumber style={{ width: '100%' }} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item name="bonusRate" label="정률지급(%)">
|
||||
<InputNumber style={{ width: '100%' }} min={0} max={100} step={0.01} />
|
||||
</Form.Item>
|
||||
<Form.Item name="effectiveFrom" label="적용시작" rules={[{ required: true }]}><Input placeholder="YYYY-MM-DD" /></Form.Item>
|
||||
<Form.Item name="effectiveTo" label="적용종료"><Input placeholder="YYYY-MM-DD" /></Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { useState } from 'react';
|
||||
import { Alert, Form, Input, InputNumber, Modal, Space, Tabs, message } from 'antd';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { ColDef } from '@ag-grid-community/core';
|
||||
import dayjs from 'dayjs';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import SearchForm from '@/components/common/SearchForm';
|
||||
import DataGrid from '@/components/common/DataGrid';
|
||||
import PermissionButton from '@/components/common/PermissionButton';
|
||||
import {
|
||||
commissionApi,
|
||||
ReinstatementRuleRow, ReinstatementLedgerRow,
|
||||
CommissionSearchParam,
|
||||
} from '@/api/commission';
|
||||
import { GRAY, RADIUS, SHADOW } from '@/theme/tokens';
|
||||
|
||||
const fmt = (v: unknown) => (v as number)?.toLocaleString() ?? '-';
|
||||
|
||||
const RULE_COLS: ColDef<ReinstatementRuleRow>[] = [
|
||||
{ field: 'carrierName', headerName: '보험사', width: 140 },
|
||||
{ field: 'productType', headerName: '상품유형', width: 120 },
|
||||
{ field: 'ratePercent', headerName: '수수료율(%)', width: 120, type: 'numericColumn' },
|
||||
{ field: 'effectiveFrom', headerName: '적용시작', width: 120 },
|
||||
{ field: 'effectiveTo', headerName: '적용종료', width: 120 },
|
||||
{ field: 'status', headerName: '상태', width: 90 },
|
||||
];
|
||||
|
||||
const LEDGER_COLS: ColDef<ReinstatementLedgerRow>[] = [
|
||||
{ field: 'settleMonth', headerName: '정산월', width: 110, pinned: 'left' },
|
||||
{ field: 'agentName', headerName: '설계사', width: 120 },
|
||||
{ field: 'contractNo', headerName: '계약번호', width: 140 },
|
||||
{ field: 'reinstatementPremium', headerName: '부활보험료', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'commissionAmount', headerName: '수수료', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'status', headerName: '상태', width: 90 },
|
||||
];
|
||||
|
||||
export default function ReinstatementCommission() {
|
||||
const qc = useQueryClient();
|
||||
const [ledgerParams, setLedgerParams] = useState<CommissionSearchParam>({
|
||||
settleMonth: dayjs().format('YYYYMM'), pageSize: 200,
|
||||
});
|
||||
const [ruleModalOpen, setRuleModalOpen] = useState(false);
|
||||
const [editingRule, setEditingRule] = useState<ReinstatementRuleRow | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { data: rulesData, isLoading: rulesLoading, isError } = useQuery({
|
||||
queryKey: ['reinstatement', 'rules'],
|
||||
queryFn: () => commissionApi.reinstatementRules(),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const { data: ledgersData, isLoading: ledgersLoading } = useQuery({
|
||||
queryKey: ['reinstatement', 'ledgers', ledgerParams],
|
||||
queryFn: () => commissionApi.reinstatementLedgers(ledgerParams),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const rules = rulesData?.list ?? [];
|
||||
const ledgers = ledgersData?.list ?? [];
|
||||
|
||||
const openCreate = () => { setEditingRule(null); form.resetFields(); setRuleModalOpen(true); };
|
||||
const openEdit = (row: ReinstatementRuleRow) => { setEditingRule(row); form.setFieldsValue(row); setRuleModalOpen(true); };
|
||||
|
||||
const handleSave = async () => {
|
||||
const values = await form.validateFields();
|
||||
try {
|
||||
if (editingRule) {
|
||||
await commissionApi.reinstatementRuleUpdate(editingRule.ruleId, values);
|
||||
} else {
|
||||
await commissionApi.reinstatementRuleCreate(values);
|
||||
}
|
||||
message.success('저장 완료');
|
||||
qc.invalidateQueries({ queryKey: ['reinstatement', 'rules'] });
|
||||
setRuleModalOpen(false);
|
||||
} catch { /* handled */ }
|
||||
};
|
||||
|
||||
const handleDelete = (id: number) => Modal.confirm({
|
||||
title: '룰 삭제', content: '삭제하시겠습니까?',
|
||||
onOk: async () => {
|
||||
await commissionApi.reinstatementRuleDelete(id);
|
||||
message.success('삭제 완료');
|
||||
qc.invalidateQueries({ queryKey: ['reinstatement', 'rules'] });
|
||||
},
|
||||
});
|
||||
|
||||
const pinnedLedger = ledgers.length > 0 ? {
|
||||
agentName: '합계',
|
||||
reinstatementPremium: ledgers.reduce((s, r) => s + (r.reinstatementPremium ?? 0), 0),
|
||||
commissionAmount: ledgers.reduce((s, r) => s + (r.commissionAmount ?? 0), 0),
|
||||
} as Partial<ReinstatementLedgerRow> : undefined;
|
||||
|
||||
const cardStyle = { background: '#fff', borderRadius: RADIUS.lg, border: `1px solid ${GRAY[100]}`, boxShadow: SHADOW.sm };
|
||||
|
||||
return (
|
||||
<PageContainer title="부활수수료" description="실효 계약 부활 시 수수료 룰 및 원장">
|
||||
{isError && (
|
||||
<Alert type="warning" showIcon message="API 미응답" description="/api/reinstatement-commissions 를 확인하세요." style={{ marginBottom: 16 }} />
|
||||
)}
|
||||
|
||||
<Tabs
|
||||
defaultActiveKey="rules"
|
||||
items={[
|
||||
{
|
||||
key: 'rules',
|
||||
label: '수수료 룰',
|
||||
children: (
|
||||
<ProCard style={cardStyle}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<PermissionButton menuCode="REINSTATE_COMM" permCode="CREATE" type="primary" onClick={openCreate}>
|
||||
+ 룰 등록
|
||||
</PermissionButton>
|
||||
</div>
|
||||
<DataGrid<ReinstatementRuleRow>
|
||||
rows={rules}
|
||||
columns={[
|
||||
...RULE_COLS,
|
||||
{
|
||||
headerName: '액션', width: 160, pinned: 'right',
|
||||
cellRenderer: (p: { data: ReinstatementRuleRow }) => (
|
||||
<Space>
|
||||
<PermissionButton menuCode="REINSTATE_COMM" permCode="UPDATE" size="small" onClick={() => openEdit(p.data)}>수정</PermissionButton>
|
||||
<PermissionButton menuCode="REINSTATE_COMM" permCode="DELETE" size="small" danger onClick={() => handleDelete(p.data.ruleId)}>삭제</PermissionButton>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
loading={rulesLoading}
|
||||
height={480}
|
||||
rowKey="ruleId"
|
||||
/>
|
||||
</ProCard>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'ledgers',
|
||||
label: '부활 원장',
|
||||
children: (
|
||||
<>
|
||||
<SearchForm
|
||||
conditions={[
|
||||
{ type: 'text', name: 'agentName', label: '설계사명', span: 6 },
|
||||
{ type: 'month', name: 'settleMonth', label: '정산월', span: 6 },
|
||||
]}
|
||||
onSearch={(v) => setLedgerParams({ ...v as CommissionSearchParam, pageSize: 200 })}
|
||||
onReset={() => setLedgerParams({ settleMonth: dayjs().format('YYYYMM'), pageSize: 200 })}
|
||||
/>
|
||||
<ProCard style={cardStyle}>
|
||||
<DataGrid<ReinstatementLedgerRow>
|
||||
rows={ledgers}
|
||||
columns={LEDGER_COLS}
|
||||
loading={ledgersLoading}
|
||||
height={480}
|
||||
rowKey="ledgerId"
|
||||
pinnedBottomRow={pinnedLedger}
|
||||
/>
|
||||
</ProCard>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={editingRule ? '룰 수정' : '룰 등록'}
|
||||
open={ruleModalOpen}
|
||||
onOk={handleSave}
|
||||
onCancel={() => setRuleModalOpen(false)}
|
||||
okText="저장" cancelText="취소"
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="carrierName" label="보험사명" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="productType" label="상품유형" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="ratePercent" label="수수료율(%)" rules={[{ required: true }]}>
|
||||
<InputNumber style={{ width: '100%' }} min={0} max={100} step={0.01} />
|
||||
</Form.Item>
|
||||
<Form.Item name="effectiveFrom" label="적용시작" rules={[{ required: true }]}><Input placeholder="YYYY-MM-DD" /></Form.Item>
|
||||
<Form.Item name="effectiveTo" label="적용종료"><Input placeholder="YYYY-MM-DD" /></Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { useState } from 'react';
|
||||
import { Alert, Button, Form, Input, InputNumber, Modal, Space, Tabs, message } from 'antd';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { ColDef } from '@ag-grid-community/core';
|
||||
import dayjs from 'dayjs';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import SearchForm from '@/components/common/SearchForm';
|
||||
import DataGrid from '@/components/common/DataGrid';
|
||||
import PermissionButton from '@/components/common/PermissionButton';
|
||||
import {
|
||||
commissionApi,
|
||||
RenewalRuleRow, RenewalLedgerRow,
|
||||
CommissionSearchParam,
|
||||
} from '@/api/commission';
|
||||
import { GRAY, RADIUS, SHADOW } from '@/theme/tokens';
|
||||
|
||||
const fmt = (v: unknown) => (v as number)?.toLocaleString() ?? '-';
|
||||
|
||||
const RULE_COLS: ColDef<RenewalRuleRow>[] = [
|
||||
{ field: 'carrierName', headerName: '보험사', width: 140 },
|
||||
{ field: 'productType', headerName: '상품유형', width: 120 },
|
||||
{ field: 'ratePercent', headerName: '수수료율(%)', width: 120, type: 'numericColumn' },
|
||||
{ field: 'effectiveFrom', headerName: '적용시작', width: 120 },
|
||||
{ field: 'effectiveTo', headerName: '적용종료', width: 120 },
|
||||
{ field: 'status', headerName: '상태', width: 90 },
|
||||
];
|
||||
|
||||
const LEDGER_COLS: ColDef<RenewalLedgerRow>[] = [
|
||||
{ field: 'settleMonth', headerName: '정산월', width: 110, pinned: 'left' },
|
||||
{ field: 'agentName', headerName: '설계사', width: 120 },
|
||||
{ field: 'contractNo', headerName: '계약번호', width: 140 },
|
||||
{ field: 'renewalPremium', headerName: '갱신보험료', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'commissionAmount', headerName: '수수료', flex: 1, type: 'numericColumn', valueFormatter: (p) => fmt(p.value) },
|
||||
{ field: 'status', headerName: '상태', width: 90 },
|
||||
];
|
||||
|
||||
export default function RenewalCommission() {
|
||||
const qc = useQueryClient();
|
||||
const [ledgerParams, setLedgerParams] = useState<CommissionSearchParam>({
|
||||
settleMonth: dayjs().format('YYYYMM'), pageSize: 200,
|
||||
});
|
||||
const [ruleModalOpen, setRuleModalOpen] = useState(false);
|
||||
const [editingRule, setEditingRule] = useState<RenewalRuleRow | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { data: rulesData, isLoading: rulesLoading, isError } = useQuery({
|
||||
queryKey: ['renewal', 'rules'],
|
||||
queryFn: () => commissionApi.renewalRules(),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const { data: ledgersData, isLoading: ledgersLoading } = useQuery({
|
||||
queryKey: ['renewal', 'ledgers', ledgerParams],
|
||||
queryFn: () => commissionApi.renewalLedgers(ledgerParams),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const rules = rulesData?.list ?? [];
|
||||
const ledgers = ledgersData?.list ?? [];
|
||||
|
||||
const openCreate = () => { setEditingRule(null); form.resetFields(); setRuleModalOpen(true); };
|
||||
const openEdit = (row: RenewalRuleRow) => { setEditingRule(row); form.setFieldsValue(row); setRuleModalOpen(true); };
|
||||
|
||||
const handleSave = async () => {
|
||||
const values = await form.validateFields();
|
||||
try {
|
||||
if (editingRule) {
|
||||
await commissionApi.renewalRuleUpdate(editingRule.ruleId, values);
|
||||
} else {
|
||||
await commissionApi.renewalRuleCreate(values);
|
||||
}
|
||||
message.success('저장 완료');
|
||||
qc.invalidateQueries({ queryKey: ['renewal', 'rules'] });
|
||||
setRuleModalOpen(false);
|
||||
} catch { /* handled */ }
|
||||
};
|
||||
|
||||
const handleDelete = (id: number) => Modal.confirm({
|
||||
title: '룰 삭제', content: '삭제하시겠습니까?',
|
||||
onOk: async () => {
|
||||
await commissionApi.renewalRuleDelete(id);
|
||||
message.success('삭제 완료');
|
||||
qc.invalidateQueries({ queryKey: ['renewal', 'rules'] });
|
||||
},
|
||||
});
|
||||
|
||||
const pinnedLedger = ledgers.length > 0 ? {
|
||||
agentName: '합계',
|
||||
renewalPremium: ledgers.reduce((s, r) => s + (r.renewalPremium ?? 0), 0),
|
||||
commissionAmount: ledgers.reduce((s, r) => s + (r.commissionAmount ?? 0), 0),
|
||||
} as Partial<RenewalLedgerRow> : undefined;
|
||||
|
||||
const cardStyle = { background: '#fff', borderRadius: RADIUS.lg, border: `1px solid ${GRAY[100]}`, boxShadow: SHADOW.sm };
|
||||
|
||||
return (
|
||||
<PageContainer title="갱신수수료" description="자동차/실비 갱신 수수료 룰 및 원장">
|
||||
{isError && (
|
||||
<Alert type="warning" showIcon message="API 미응답" description="/api/renewal-commissions 를 확인하세요." style={{ marginBottom: 16 }} />
|
||||
)}
|
||||
|
||||
<Tabs
|
||||
defaultActiveKey="rules"
|
||||
items={[
|
||||
{
|
||||
key: 'rules',
|
||||
label: '수수료 룰',
|
||||
children: (
|
||||
<ProCard style={cardStyle}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<PermissionButton menuCode="RENEWAL_COMM" permCode="CREATE" type="primary" onClick={openCreate}>
|
||||
+ 룰 등록
|
||||
</PermissionButton>
|
||||
</div>
|
||||
<DataGrid<RenewalRuleRow>
|
||||
rows={rules}
|
||||
columns={[
|
||||
...RULE_COLS,
|
||||
{
|
||||
headerName: '액션', width: 160, pinned: 'right',
|
||||
cellRenderer: (p: { data: RenewalRuleRow }) => (
|
||||
<Space>
|
||||
<PermissionButton menuCode="RENEWAL_COMM" permCode="UPDATE" size="small" onClick={() => openEdit(p.data)}>수정</PermissionButton>
|
||||
<PermissionButton menuCode="RENEWAL_COMM" permCode="DELETE" size="small" danger onClick={() => handleDelete(p.data.ruleId)}>삭제</PermissionButton>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
loading={rulesLoading}
|
||||
height={480}
|
||||
rowKey="ruleId"
|
||||
/>
|
||||
</ProCard>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'ledgers',
|
||||
label: '갱신 원장',
|
||||
children: (
|
||||
<>
|
||||
<SearchForm
|
||||
conditions={[
|
||||
{ type: 'text', name: 'agentName', label: '설계사명', span: 6 },
|
||||
{ type: 'month', name: 'settleMonth', label: '정산월', span: 6 },
|
||||
]}
|
||||
onSearch={(v) => setLedgerParams({ ...v as CommissionSearchParam, pageSize: 200 })}
|
||||
onReset={() => setLedgerParams({ settleMonth: dayjs().format('YYYYMM'), pageSize: 200 })}
|
||||
/>
|
||||
<ProCard style={cardStyle}>
|
||||
<DataGrid<RenewalLedgerRow>
|
||||
rows={ledgers}
|
||||
columns={LEDGER_COLS}
|
||||
loading={ledgersLoading}
|
||||
height={480}
|
||||
rowKey="ledgerId"
|
||||
pinnedBottomRow={pinnedLedger}
|
||||
/>
|
||||
</ProCard>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={editingRule ? '룰 수정' : '룰 등록'}
|
||||
open={ruleModalOpen}
|
||||
onOk={handleSave}
|
||||
onCancel={() => setRuleModalOpen(false)}
|
||||
okText="저장" cancelText="취소"
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="carrierName" label="보험사명" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="productType" label="상품유형" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="ratePercent" label="수수료율(%)" rules={[{ required: true }]}>
|
||||
<InputNumber style={{ width: '100%' }} min={0} max={100} step={0.01} />
|
||||
</Form.Item>
|
||||
<Form.Item name="effectiveFrom" label="적용시작" rules={[{ required: true }]}><Input placeholder="YYYY-MM-DD" /></Form.Item>
|
||||
<Form.Item name="effectiveTo" label="적용종료"><Input placeholder="YYYY-MM-DD" /></Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user