feat(frontend): 누락된 메뉴 14개 페이지 일괄 구현
신규 페이지 (모두 ProTable / ProCard 패턴): - org/OrgTree: 조직 트리 + 선택 시 우측에 소속 설계사 ProTable - product/CompanyList: 보험사 목록 - product/ProductList: 상품 목록 (보험종 필터) - rule/CommissionRateList: 수수료율 (보험사 통보) - rule/PayoutRuleList: 지급율 (직급 × 보험종 × 회차) - rule/OverrideRuleList: 오버라이드 규정 (직급 페어) - rule/ChargebackRuleList: 환수 규정 (실효 월수 구간) - rule/ExceptionCodeList: 예외 코드 마스터 - ledger/MaintainLedger: 유지수수료 원장 - receive/ReceiveData: 보험사 raw 수신 데이터 - receive/ReceiveMapping: 보험사 프로파일 + 필드 매핑 규칙 - system/MenuManage: 메뉴 트리 표시 (Tree) - system/SystemConfig: 키-값 설정 (그룹별 필터) - system/SystemLog: 로그인/API/변경 이력 3 탭 신규 API 모듈: - api/org.ts (조직 트리) - api/company.ts, api/product.ts - api/rule.ts (5종 통합) - api/receive.ts (profile/field/raw/error) - api/config.ts, api/log.ts App.tsx: 14 라우트 추가 (org/tree, companies, products, rules/*, receive/*, ledger/maintain, system/menus, system/config, system/logs) 검증: - tsc -b 통과 - 12개 백엔드 엔드포인트 200 OK 확인 - dev 서버 HMR 자동 반영 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import CodeBadge from '@/components/common/CodeBadge';
|
||||
import MoneyText from '@/components/common/MoneyText';
|
||||
import ExcelExportButton from '@/components/common/ExcelExportButton';
|
||||
import { useCommonCodes } from '@/hooks/useCommonCodes';
|
||||
import { ledgerApi, LedgerResp, LedgerSearchParam } from '@/api/ledger';
|
||||
|
||||
export default function MaintainLedger() {
|
||||
const { data: insuranceCodes = [] } = useCommonCodes('INSURANCE_TYPE');
|
||||
const { data: reconCodes = [] } = useCommonCodes('RECON_STATUS');
|
||||
|
||||
const columns: ProColumns<LedgerResp>[] = [
|
||||
{ title: '정산월', dataIndex: 'settleMonth', width: 90, fixed: 'left',
|
||||
valueType: 'dateMonth', fieldProps: { format: 'YYYYMM' } },
|
||||
{ title: '증권번호', dataIndex: 'policyNo', width: 130, fixed: 'left',
|
||||
fieldProps: { placeholder: '증권번호' } },
|
||||
{
|
||||
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: 'reconcileStatus', width: 100, valueType: 'select',
|
||||
valueEnum: Object.fromEntries(reconCodes.map((c) => [c.code, { text: c.codeName }])),
|
||||
render: (_, r) => <CodeBadge groupCode="RECON_STATUS" value={r.reconcileStatus} />,
|
||||
},
|
||||
{ title: '설계사', dataIndex: 'agentName', width: 100, search: false },
|
||||
{ title: '소속', dataIndex: 'orgName', width: 160, search: false },
|
||||
{ title: '보험사', dataIndex: 'companyName', width: 120, search: false },
|
||||
{ title: '상품', dataIndex: 'productName', width: 200, search: false },
|
||||
{
|
||||
title: '보험료', dataIndex: 'premium', width: 110, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.premium} />,
|
||||
},
|
||||
{
|
||||
title: '회사율', dataIndex: 'companyRate', width: 80, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.companyRate} fraction={2} />,
|
||||
},
|
||||
{
|
||||
title: '회사수수료', dataIndex: 'companyAmount', width: 120, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.companyAmount} />,
|
||||
},
|
||||
{
|
||||
title: '지급율', dataIndex: 'payoutRate', width: 80, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.payoutRate} fraction={2} />,
|
||||
},
|
||||
{
|
||||
title: '설계사수수료', dataIndex: 'agentAmount', width: 120, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.agentAmount} />,
|
||||
},
|
||||
{
|
||||
title: '세액', dataIndex: 'taxAmount', width: 100, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.taxAmount} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="유지수수료 원장" description="2~5년차 유지 계약에 대한 유지수수료">
|
||||
<ProTable<LedgerResp, LedgerSearchParam>
|
||||
rowKey="ledgerId"
|
||||
columns={columns}
|
||||
scroll={{ x: 1500 }}
|
||||
request={async (params) => {
|
||||
const { current, pageSize, ...rest } = params as any;
|
||||
const res = await ledgerApi.maintain({ ...rest, pageNum: current, pageSize });
|
||||
return { data: res.list, total: res.total, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t.toLocaleString()}건` }}
|
||||
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
toolBarRender={() => [
|
||||
<ExcelExportButton key="export" url="/api/ledger/maintain/export" fileName="유지수수료원장" />,
|
||||
]}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Tree, Card, Empty, Tag, Spin } from 'antd';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ProCard, ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import { IconBuilding } from '@tabler/icons-react';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import CodeBadge from '@/components/common/CodeBadge';
|
||||
import MoneyText from '@/components/common/MoneyText';
|
||||
import { orgApi, OrgNode } from '@/api/org';
|
||||
import { agentApi, AgentResp } from '@/api/agent';
|
||||
|
||||
interface AntdTreeNode { key: string; title: any; children?: AntdTreeNode[]; orgId: number; orgType: string; }
|
||||
|
||||
function toTree(nodes: OrgNode[] = []): AntdTreeNode[] {
|
||||
return nodes.map((n) => ({
|
||||
key: String(n.orgId),
|
||||
orgId: n.orgId,
|
||||
orgType: n.orgType,
|
||||
title: (
|
||||
<span>
|
||||
<Tag color={n.orgType === 'HQ' ? 'blue' : n.orgType === 'BR' ? 'cyan' : 'green'} style={{ marginRight: 6 }}>
|
||||
{n.orgType}
|
||||
</Tag>
|
||||
{n.orgName}
|
||||
</span>
|
||||
),
|
||||
children: n.children ? toTree(n.children) : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
export default function OrgTree() {
|
||||
const [selectedOrgId, setSelectedOrgId] = useState<number | undefined>();
|
||||
|
||||
const { data: tree = [], isLoading: treeLoading } = useQuery({
|
||||
queryKey: ['orgs', 'tree'],
|
||||
queryFn: orgApi.tree,
|
||||
});
|
||||
const treeData = useMemo(() => toTree(tree), [tree]);
|
||||
|
||||
const columns: ProColumns<AgentResp>[] = [
|
||||
{ title: '설계사명', dataIndex: 'agentName', width: 110 },
|
||||
{ title: '직급', dataIndex: 'gradeName', width: 90 },
|
||||
{
|
||||
title: '상태', dataIndex: 'status', width: 90,
|
||||
render: (_, r) => <CodeBadge groupCode="AGENT_STATUS" value={r.status} />,
|
||||
},
|
||||
{ title: '사번', dataIndex: 'licenseNo', width: 110 },
|
||||
{ title: '전화번호', dataIndex: 'phone', width: 130 },
|
||||
{ title: '계약수', dataIndex: 'contractCount', width: 80, align: 'right',
|
||||
render: (_, r) => <MoneyText value={r.contractCount} /> },
|
||||
{ title: '누적수수료', dataIndex: 'totalCommission', align: 'right',
|
||||
render: (_, r) => <MoneyText value={r.totalCommission} /> },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="조직 트리" description="본부 / 지점 / 팀 트리 + 소속 설계사">
|
||||
<ProCard split="vertical" ghost>
|
||||
<ProCard colSpan="280px" bordered title={<><IconBuilding size={16} style={{ verticalAlign: -2 }} /> 조직</>}>
|
||||
{treeLoading ? <Spin /> : (
|
||||
treeData.length ? (
|
||||
<Tree
|
||||
treeData={treeData as any}
|
||||
defaultExpandAll
|
||||
onSelect={(keys, e) => {
|
||||
const node = e.node as unknown as AntdTreeNode;
|
||||
setSelectedOrgId(node.orgId);
|
||||
}}
|
||||
/>
|
||||
) : <Empty description="조직 없음" />
|
||||
)}
|
||||
</ProCard>
|
||||
<ProCard ghost>
|
||||
{selectedOrgId ? (
|
||||
<Card bordered={false} title={<span style={{ fontWeight: 600 }}>소속 설계사</span>}>
|
||||
<ProTable<AgentResp>
|
||||
rowKey="agentId"
|
||||
columns={columns}
|
||||
search={false}
|
||||
options={false}
|
||||
request={async (params) => {
|
||||
const res = await agentApi.list({
|
||||
orgId: selectedOrgId,
|
||||
pageNum: params.current ?? 1,
|
||||
pageSize: params.pageSize ?? 50,
|
||||
});
|
||||
return { data: res.list, total: res.total, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t}명` }}
|
||||
/>
|
||||
</Card>
|
||||
) : (
|
||||
<Empty description="좌측에서 조직을 선택하세요" />
|
||||
)}
|
||||
</ProCard>
|
||||
</ProCard>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import { Tag } from 'antd';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import { companyApi, CompanyResp, CompanySearchParam } from '@/api/company';
|
||||
|
||||
export default function CompanyList() {
|
||||
const columns: ProColumns<CompanyResp>[] = [
|
||||
{ title: '검색어', dataIndex: 'searchKeyword', hideInTable: true,
|
||||
fieldProps: { placeholder: '회사명 / 사업자번호' } },
|
||||
{ title: '회사코드', dataIndex: 'companyCode', width: 100, search: false },
|
||||
{ title: '회사명', dataIndex: 'companyName', width: 180,
|
||||
render: (_, r) => <strong>{r.companyName}</strong> },
|
||||
{ title: '구분', dataIndex: 'companyType', width: 100, search: false },
|
||||
{ title: '사업자번호', dataIndex: 'bizNo', width: 130, search: false },
|
||||
{ title: '담당자', dataIndex: 'contactName', width: 100, search: false },
|
||||
{ title: '연락처', dataIndex: 'contactPhone', width: 130, search: false },
|
||||
{ title: '이메일', dataIndex: 'contactEmail', width: 200, search: false },
|
||||
{
|
||||
title: '활성', dataIndex: 'isActive', width: 80,
|
||||
render: (_, r) => <Tag color={r.isActive === 'Y' ? 'success' : 'default'}>
|
||||
{r.isActive === 'Y' ? '활성' : '비활성'}</Tag>,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="보험사 관리" description="제휴 보험사 정보 / 데이터 수신 프로파일">
|
||||
<ProTable<CompanyResp, CompanySearchParam>
|
||||
rowKey="companyId"
|
||||
columns={columns}
|
||||
request={async (params) => {
|
||||
const { current, pageSize, ...rest } = params as any;
|
||||
const res = await companyApi.list({ ...rest, pageNum: current, pageSize });
|
||||
return { data: res.list, total: res.total, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 20, showTotal: (t) => `총 ${t.toLocaleString()}건` }}
|
||||
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import { Tag } from 'antd';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import CodeBadge from '@/components/common/CodeBadge';
|
||||
import { useCommonCodes } from '@/hooks/useCommonCodes';
|
||||
import { productApi, ProductResp, ProductSearchParam } from '@/api/product';
|
||||
|
||||
export default function ProductList() {
|
||||
const { data: insuranceCodes = [] } = useCommonCodes('INSURANCE_TYPE');
|
||||
|
||||
const columns: ProColumns<ProductResp>[] = [
|
||||
{ title: '검색어', dataIndex: 'searchKeyword', hideInTable: true,
|
||||
fieldProps: { placeholder: '상품명 / 코드' } },
|
||||
{
|
||||
title: '보험종류', dataIndex: 'insuranceType', width: 110, valueType: 'select',
|
||||
valueEnum: Object.fromEntries(insuranceCodes.map((c) => [c.code, { text: c.codeName }])),
|
||||
render: (_, r) => <CodeBadge groupCode="INSURANCE_TYPE" value={r.insuranceType} />,
|
||||
},
|
||||
{ title: '상품코드', dataIndex: 'productCode', width: 120, search: false },
|
||||
{ title: '상품명', dataIndex: 'productName', width: 240, search: false,
|
||||
render: (_, r) => <strong>{r.productName}</strong> },
|
||||
{ title: '보험사', dataIndex: 'companyName', width: 120, search: false },
|
||||
{ title: '상품군', dataIndex: 'productGroup', width: 100, search: false },
|
||||
{ title: '납입주기', dataIndex: 'payPeriodType', width: 100, search: false },
|
||||
{ title: '판매개시', dataIndex: 'launchDate', width: 110, search: false },
|
||||
{ title: '판매종료', dataIndex: 'endDate', width: 110, search: false },
|
||||
{
|
||||
title: '활성', dataIndex: 'isActive', width: 80, search: false,
|
||||
render: (_, r) => <Tag color={r.isActive === 'Y' ? 'success' : 'default'}>
|
||||
{r.isActive === 'Y' ? '활성' : '비활성'}</Tag>,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="상품 관리" description="보험사별 보험 상품 / 보험종류 / 상품군">
|
||||
<ProTable<ProductResp, ProductSearchParam>
|
||||
rowKey="productId"
|
||||
columns={columns}
|
||||
scroll={{ x: 1300 }}
|
||||
request={async (params) => {
|
||||
const { current, pageSize, ...rest } = params as any;
|
||||
const res = await productApi.list({ ...rest, pageNum: current, pageSize });
|
||||
return { data: res.list, total: res.total, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 20, showTotal: (t) => `총 ${t.toLocaleString()}건` }}
|
||||
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import { Tag } from 'antd';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import { useCommonCodes } from '@/hooks/useCommonCodes';
|
||||
import { receiveApi, RawCommissionResp, ParseErrorResp } from '@/api/receive';
|
||||
|
||||
const STATUS_COLOR = { PENDING: 'processing', OK: 'success', ERROR: 'error' } as const;
|
||||
|
||||
export default function ReceiveData() {
|
||||
const { data: companies = [] } = useCommonCodes('COMPANY_CODE');
|
||||
|
||||
const rawColumns: ProColumns<RawCommissionResp>[] = [
|
||||
{
|
||||
title: '보험사', dataIndex: 'companyCode', width: 110, valueType: 'select',
|
||||
valueEnum: Object.fromEntries(companies.map((c) => [c.code, { text: c.codeName }])),
|
||||
},
|
||||
{ title: '정산월', dataIndex: 'settleMonth', width: 90, valueType: 'dateMonth',
|
||||
fieldProps: { format: 'YYYYMM' } },
|
||||
{
|
||||
title: '파싱상태', dataIndex: 'parseStatus', width: 110, valueType: 'select',
|
||||
valueEnum: { PENDING: { text: '대기' }, OK: { text: '성공' }, ERROR: { text: '실패' } },
|
||||
render: (_, r) => <Tag color={STATUS_COLOR[r.parseStatus as 'PENDING' | 'OK' | 'ERROR'] ?? 'default'}>
|
||||
{r.parseStatus}</Tag>,
|
||||
},
|
||||
{ title: '데이터 유형', dataIndex: 'dataType', width: 110, search: false },
|
||||
{ title: '배치 ID', dataIndex: 'batchId', width: 140, search: false },
|
||||
{ title: '파일명', dataIndex: 'fileName', width: 200, search: false, ellipsis: true },
|
||||
{ title: '행 번호', dataIndex: 'rowNumber', width: 80, align: 'right', search: false },
|
||||
{ title: '연결 원장', dataIndex: 'mappedLedgerId', width: 100, align: 'right', search: false },
|
||||
{ title: '수신일시', dataIndex: 'receivedAt', width: 160, search: false },
|
||||
{ title: '파싱일시', dataIndex: 'parsedAt', width: 160, search: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="수신 데이터" description="보험사 수수료 raw 데이터 / 파싱 상태">
|
||||
<ProTable<RawCommissionResp>
|
||||
rowKey="rawId"
|
||||
columns={rawColumns}
|
||||
scroll={{ x: 1300 }}
|
||||
request={async (params) => {
|
||||
const list = await receiveApi.raw({
|
||||
companyCode: (params as any).companyCode,
|
||||
settleMonth: (params as any).settleMonth,
|
||||
parseStatus: (params as any).parseStatus,
|
||||
});
|
||||
return { data: list, total: list.length, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t.toLocaleString()}건` }}
|
||||
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useState } from 'react';
|
||||
import { Empty, Tag } from 'antd';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ProCard, ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import { receiveApi, CompanyProfileResp, FieldMappingResp } from '@/api/receive';
|
||||
|
||||
export default function ReceiveMapping() {
|
||||
const [selected, setSelected] = useState<string | undefined>();
|
||||
|
||||
const { data: profiles = [] } = useQuery({
|
||||
queryKey: ['receive', 'profiles'],
|
||||
queryFn: receiveApi.profiles,
|
||||
});
|
||||
|
||||
const profileColumns: ProColumns<CompanyProfileResp>[] = [
|
||||
{ title: '보험사', dataIndex: 'companyCode', width: 90,
|
||||
render: (_, r) => <strong>{r.companyCode}</strong> },
|
||||
{ title: '데이터 형식', dataIndex: 'dataFormat', width: 110 },
|
||||
{ title: '수신방법', dataIndex: 'receiveMethod', width: 110 },
|
||||
{
|
||||
title: '활성', dataIndex: 'isActive', width: 70, align: 'center',
|
||||
render: (_, r) => <Tag color={r.isActive === 'Y' ? 'success' : 'default'}>
|
||||
{r.isActive === 'Y' ? 'Y' : 'N'}</Tag>,
|
||||
},
|
||||
];
|
||||
|
||||
const fieldColumns: ProColumns<FieldMappingResp>[] = [
|
||||
{ title: '#', dataIndex: 'sortOrder', width: 50, align: 'center' },
|
||||
{ title: 'Source 필드', dataIndex: 'sourceField', width: 130 },
|
||||
{ title: 'Source 컬럼', dataIndex: 'sourceColIndex', width: 90, align: 'right' },
|
||||
{ title: 'Target 테이블', dataIndex: 'targetTable', width: 120 },
|
||||
{ title: 'Target 필드', dataIndex: 'targetField', width: 130 },
|
||||
{ title: 'Type', dataIndex: 'dataType', width: 100 },
|
||||
{ title: '변환규칙', dataIndex: 'transformRule', width: 110,
|
||||
render: (_, r) => r.transformRule ? <Tag color="blue">{r.transformRule}</Tag> : '-' },
|
||||
{ title: '기본값', dataIndex: 'defaultValue', width: 110 },
|
||||
{ title: '필수', dataIndex: 'isRequired', width: 70, align: 'center',
|
||||
render: (_, r) => r.isRequired === 'Y' ? <Tag color="error">필수</Tag> : '' },
|
||||
{ title: '버전', dataIndex: 'version', width: 60, align: 'center' },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="매핑 관리" description="보험사 raw 데이터 → 표준 원장 매핑 규칙">
|
||||
<ProCard split="vertical" ghost>
|
||||
<ProCard colSpan="380px" bordered title={<span style={{ fontWeight: 600 }}>보험사 프로파일</span>}>
|
||||
<ProTable<CompanyProfileResp>
|
||||
rowKey="companyCode"
|
||||
columns={profileColumns}
|
||||
dataSource={profiles}
|
||||
search={false}
|
||||
options={false}
|
||||
pagination={false}
|
||||
size="small"
|
||||
onRow={(r) => ({
|
||||
onClick: () => setSelected(r.companyCode),
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
background: selected === r.companyCode ? '#E6F1FB' : undefined,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</ProCard>
|
||||
<ProCard
|
||||
ghost
|
||||
title={selected ? <span style={{ fontWeight: 600 }}>필드 매핑 — {selected}</span>
|
||||
: <span>좌측에서 보험사 선택</span>}
|
||||
>
|
||||
{selected ? (
|
||||
<ProTable<FieldMappingResp>
|
||||
rowKey="mappingId"
|
||||
columns={fieldColumns}
|
||||
search={false}
|
||||
options={false}
|
||||
pagination={false}
|
||||
size="small"
|
||||
request={async () => {
|
||||
const list = await receiveApi.fields(selected);
|
||||
return { data: list, total: list.length, success: true };
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Empty description="좌측에서 보험사를 선택하세요" />
|
||||
)}
|
||||
</ProCard>
|
||||
</ProCard>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Tag } from 'antd';
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import CodeBadge from '@/components/common/CodeBadge';
|
||||
import MoneyText from '@/components/common/MoneyText';
|
||||
import { ruleApi, ChargebackRuleResp } from '@/api/rule';
|
||||
|
||||
export default function ChargebackRuleList() {
|
||||
const columns: ProColumns<ChargebackRuleResp>[] = [
|
||||
{ title: '보험사', dataIndex: 'companyCode', width: 100, search: false,
|
||||
render: (_, r) => <strong>{r.companyCode}</strong> },
|
||||
{
|
||||
title: '보험종', dataIndex: 'insuranceType', width: 110, search: false,
|
||||
render: (_, r) => <CodeBadge groupCode="INSURANCE_TYPE" value={r.insuranceType} />,
|
||||
},
|
||||
{ title: '실효 시작월', dataIndex: 'lapseMonthFrom', width: 100, align: 'center', search: false },
|
||||
{ title: '실효 종료월', dataIndex: 'lapseMonthTo', width: 100, align: 'center', search: false },
|
||||
{
|
||||
title: '환수율(%)', dataIndex: 'cbRate', width: 110, align: 'right', search: false,
|
||||
render: (_, r) => <strong><MoneyText value={r.cbRate} fraction={4} /></strong>,
|
||||
},
|
||||
{
|
||||
title: '오버라이드 포함', dataIndex: 'includeOverride', width: 130, align: 'center', search: false,
|
||||
render: (_, r) => <Tag color={r.includeOverride === 'Y' ? 'success' : 'default'}>
|
||||
{r.includeOverride === 'Y' ? '포함' : '제외'}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '분할 가능', dataIndex: 'installmentAllowed', width: 100, align: 'center', search: false,
|
||||
render: (_, r) => <Tag color={r.installmentAllowed === 'Y' ? 'success' : 'default'}>
|
||||
{r.installmentAllowed === 'Y' ? '허용' : '불가'}</Tag>,
|
||||
},
|
||||
{ title: '최대 분할', dataIndex: 'maxInstallments', width: 90, align: 'right', search: false },
|
||||
{ title: '적용시작', dataIndex: 'effectiveFrom', width: 110, search: false },
|
||||
{ title: '적용종료', dataIndex: 'effectiveTo', width: 110, search: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="환수 규정" description="실효 계약 환수율 / 분할환수 정책">
|
||||
<ProTable<ChargebackRuleResp>
|
||||
rowKey="cbRuleId"
|
||||
columns={columns}
|
||||
search={false}
|
||||
request={async () => {
|
||||
const list = await ruleApi.chargebackRules();
|
||||
return { data: list, total: list.length, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t}건` }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import MoneyText from '@/components/common/MoneyText';
|
||||
import { ruleApi, CommissionRateResp } from '@/api/rule';
|
||||
|
||||
export default function CommissionRateList() {
|
||||
const columns: ProColumns<CommissionRateResp>[] = [
|
||||
{ title: '상품ID', dataIndex: 'productId', width: 90 },
|
||||
{ title: '상품명', dataIndex: 'productName', width: 200, search: false },
|
||||
{ title: '회차', dataIndex: 'commissionYear', width: 70, align: 'center', search: false },
|
||||
{
|
||||
title: '수수료율(%)', dataIndex: 'ratePct', width: 110, align: 'right', search: false,
|
||||
render: (_, r) => <strong><MoneyText value={r.ratePct} fraction={4} /></strong>,
|
||||
},
|
||||
{ title: '납입조건', dataIndex: 'payMethodCond', width: 110, search: false },
|
||||
{
|
||||
title: '최소보험료', dataIndex: 'minPremium', width: 130, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.minPremium} />,
|
||||
},
|
||||
{ title: '적용시작', dataIndex: 'effectiveFrom', width: 110, search: false },
|
||||
{ title: '적용종료', dataIndex: 'effectiveTo', width: 110, search: false },
|
||||
{ title: '버전', dataIndex: 'version', width: 60, align: 'center', search: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="수수료율 (보험사 → 시스템)" description="상품·회차별 보험사 통보 수수료율">
|
||||
<ProTable<CommissionRateResp>
|
||||
rowKey="rateId"
|
||||
columns={columns}
|
||||
search={false}
|
||||
request={async () => {
|
||||
const list = await ruleApi.commissionRates();
|
||||
return { data: list, total: list.length, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t.toLocaleString()}건` }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Tag } from 'antd';
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import { ruleApi, ExceptionCodeResp } from '@/api/rule';
|
||||
|
||||
const DIRECTION_COLOR = { PLUS: 'success', MINUS: 'error' } as const;
|
||||
|
||||
export default function ExceptionCodeList() {
|
||||
const columns: ProColumns<ExceptionCodeResp>[] = [
|
||||
{ title: '예외코드', dataIndex: 'exceptionCode', width: 120, search: false,
|
||||
render: (_, r) => <strong>{r.exceptionCode}</strong> },
|
||||
{ title: '예외명', dataIndex: 'exceptionName', width: 160, search: false },
|
||||
{ title: '카테고리', dataIndex: 'category', width: 110, search: false },
|
||||
{
|
||||
title: '방향', dataIndex: 'direction', width: 80, align: 'center', search: false,
|
||||
render: (_, r) => <Tag color={DIRECTION_COLOR[r.direction as 'PLUS' | 'MINUS'] ?? 'default'}>
|
||||
{r.direction === 'PLUS' ? '가산 (+)' : '차감 (-)'}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '자동계산', dataIndex: 'autoCalcYn', width: 90, align: 'center', search: false,
|
||||
render: (_, r) => r.autoCalcYn === 'Y' ? <Tag color="processing">자동</Tag> : <Tag>수동</Tag>,
|
||||
},
|
||||
{
|
||||
title: '승인 필요', dataIndex: 'approvalRequired', width: 90, align: 'center', search: false,
|
||||
render: (_, r) => r.approvalRequired === 'Y' ? <Tag color="warning">필요</Tag> : <Tag>불요</Tag>,
|
||||
},
|
||||
{
|
||||
title: '과세', dataIndex: 'taxIncluded', width: 80, align: 'center', search: false,
|
||||
render: (_, r) => r.taxIncluded === 'Y' ? <Tag color="success">과세</Tag> : <Tag>비과세</Tag>,
|
||||
},
|
||||
{ title: '설명', dataIndex: 'description', ellipsis: true, search: false },
|
||||
{
|
||||
title: '활성', dataIndex: 'isActive', width: 70, align: 'center', search: false,
|
||||
render: (_, r) => <Tag color={r.isActive === 'Y' ? 'success' : 'default'}>
|
||||
{r.isActive === 'Y' ? 'Y' : 'N'}</Tag>,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="예외 코드" description="가산/차감/승인/세금 정책 마스터 (예외금액 원장에서 사용)">
|
||||
<ProTable<ExceptionCodeResp>
|
||||
rowKey="exceptionCode"
|
||||
columns={columns}
|
||||
search={false}
|
||||
request={async () => {
|
||||
const list = await ruleApi.exceptionCodes();
|
||||
return { data: list, total: list.length, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t}건` }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import MoneyText from '@/components/common/MoneyText';
|
||||
import { ruleApi, OverrideRuleResp } from '@/api/rule';
|
||||
|
||||
export default function OverrideRuleList() {
|
||||
const columns: ProColumns<OverrideRuleResp>[] = [
|
||||
{ title: '하위 직급', dataIndex: 'fromGradeName', width: 110, search: false,
|
||||
render: (_, r) => <strong>{r.fromGradeName ?? `#${r.fromGrade}`}</strong> },
|
||||
{ title: '상위 직급', dataIndex: 'toGradeName', width: 110, search: false,
|
||||
render: (_, r) => r.toGradeName ?? `#${r.toGrade}` },
|
||||
{
|
||||
title: '오버라이드율(%)', dataIndex: 'overridePct', width: 130, align: 'right', search: false,
|
||||
render: (_, r) => <strong><MoneyText value={r.overridePct} fraction={4} /></strong>,
|
||||
},
|
||||
{ title: '계산방식', dataIndex: 'calcType', width: 110, search: false },
|
||||
{ title: '보험종조건', dataIndex: 'insuranceTypeCond', width: 120, search: false },
|
||||
{
|
||||
title: '월 상한', dataIndex: 'capAmount', width: 130, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.capAmount} />,
|
||||
},
|
||||
{ title: '적용시작', dataIndex: 'effectiveFrom', width: 110, search: false },
|
||||
{ title: '적용종료', dataIndex: 'effectiveTo', width: 110, search: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="오버라이드 규정" description="조직 트리 상향 분배율 (하위 → 상위 직급)">
|
||||
<ProTable<OverrideRuleResp>
|
||||
rowKey="overrideId"
|
||||
columns={columns}
|
||||
search={false}
|
||||
request={async () => {
|
||||
const list = await ruleApi.overrideRules();
|
||||
return { data: list, total: list.length, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t}건` }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import CodeBadge from '@/components/common/CodeBadge';
|
||||
import MoneyText from '@/components/common/MoneyText';
|
||||
import { useCommonCodes } from '@/hooks/useCommonCodes';
|
||||
import { ruleApi, PayoutRuleResp } from '@/api/rule';
|
||||
|
||||
export default function PayoutRuleList() {
|
||||
const { data: insuranceCodes = [] } = useCommonCodes('INSURANCE_TYPE');
|
||||
|
||||
const columns: ProColumns<PayoutRuleResp>[] = [
|
||||
{ title: '직급', dataIndex: 'gradeName', width: 100, search: false,
|
||||
render: (_, r) => <strong>{r.gradeName}</strong> },
|
||||
{
|
||||
title: '보험종류', dataIndex: 'insuranceType', width: 110,
|
||||
valueType: 'select',
|
||||
valueEnum: Object.fromEntries(insuranceCodes.map((c) => [c.code, { text: c.codeName }])),
|
||||
render: (_, r) => <CodeBadge groupCode="INSURANCE_TYPE" value={r.insuranceType} />,
|
||||
},
|
||||
{ title: '회차', dataIndex: 'commissionYear', width: 70, align: 'center', search: false },
|
||||
{
|
||||
title: '지급율(%)', dataIndex: 'payoutPct', width: 100, align: 'right', search: false,
|
||||
render: (_, r) => <strong><MoneyText value={r.payoutPct} fraction={4} /></strong>,
|
||||
},
|
||||
{ title: '납입조건', dataIndex: 'payMethodCond', width: 110, search: false },
|
||||
{ title: '실적등급', dataIndex: 'performanceGrade', width: 100, search: false },
|
||||
{ title: '적용시작', dataIndex: 'effectiveFrom', width: 110, search: false },
|
||||
{ title: '적용종료', dataIndex: 'effectiveTo', width: 110, search: false },
|
||||
{ title: '버전', dataIndex: 'version', width: 60, align: 'center', search: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="지급율 (시스템 → 설계사)" description="직급·보험종·회차별 설계사 지급율">
|
||||
<ProTable<PayoutRuleResp>
|
||||
rowKey="ruleId"
|
||||
columns={columns}
|
||||
request={async (params) => {
|
||||
const list = await ruleApi.payoutRules({ insuranceType: (params as any).insuranceType });
|
||||
return { data: list, total: list.length, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t.toLocaleString()}건` }}
|
||||
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Tag, Tree } from 'antd';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { IconCategory } from '@tabler/icons-react';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import { menuApi, MenuResp } from '@/api/menu';
|
||||
|
||||
interface TreeNode { key: string; title: any; children?: TreeNode[]; }
|
||||
|
||||
function toTree(menus: MenuResp[]): TreeNode[] {
|
||||
return menus.map((m) => ({
|
||||
key: String(m.menuId),
|
||||
title: (
|
||||
<span>
|
||||
<Tag color={m.menuType === 'DIRECTORY' ? 'blue' : 'default'} style={{ marginRight: 6 }}>
|
||||
{m.menuType}
|
||||
</Tag>
|
||||
<strong>{m.menuName}</strong>
|
||||
<span style={{ color: '#999', marginLeft: 8, fontSize: 12 }}>
|
||||
{m.menuPath ?? m.menuCode}
|
||||
</span>
|
||||
</span>
|
||||
),
|
||||
children: m.children?.length ? toTree(m.children) : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
export default function MenuManage() {
|
||||
const { data: tree = [] } = useQuery({
|
||||
queryKey: ['menu', 'tree'],
|
||||
queryFn: menuApi.tree,
|
||||
});
|
||||
|
||||
return (
|
||||
<PageContainer title="메뉴 관리" description="시스템 메뉴 트리 / 표시 / 권한 매핑">
|
||||
<ProCard
|
||||
title={<span><IconCategory size={16} style={{ verticalAlign: -3 }} /> 메뉴 트리</span>}
|
||||
headerBordered
|
||||
bordered={false}
|
||||
extra={<Tag color="processing">전체 {countMenus(tree)}개</Tag>}
|
||||
>
|
||||
<Tree
|
||||
treeData={toTree(tree) as any}
|
||||
defaultExpandAll
|
||||
showLine={{ showLeafIcon: false }}
|
||||
style={{ minHeight: 400 }}
|
||||
/>
|
||||
</ProCard>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function countMenus(menus: MenuResp[]): number {
|
||||
return menus.reduce((sum, m) => sum + 1 + (m.children ? countMenus(m.children) : 0), 0);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Tag } from 'antd';
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import { configApi, SystemConfigResp } from '@/api/config';
|
||||
|
||||
const GROUP_COLOR: Record<string, string> = {
|
||||
GENERAL: 'blue', BATCH: 'cyan', SECURITY: 'red', MAIL: 'green', TAX: 'orange',
|
||||
};
|
||||
|
||||
export default function SystemConfig() {
|
||||
const columns: ProColumns<SystemConfigResp>[] = [
|
||||
{
|
||||
title: '그룹', dataIndex: 'configGroup', width: 110, valueType: 'select',
|
||||
valueEnum: {
|
||||
GENERAL: { text: '일반' }, BATCH: { text: '배치' }, SECURITY: { text: '보안' },
|
||||
MAIL: { text: '메일' }, TAX: { text: '세금' },
|
||||
},
|
||||
render: (_, r) => <Tag color={GROUP_COLOR[r.configGroup ?? ''] ?? 'default'}>
|
||||
{r.configGroup}</Tag>,
|
||||
},
|
||||
{ title: '설정 키', dataIndex: 'configKey', width: 220, search: false,
|
||||
render: (_, r) => <code style={{ background: '#f5f5f5', padding: '2px 6px', borderRadius: 4 }}>
|
||||
{r.configKey}</code> },
|
||||
{
|
||||
title: '설정 값', dataIndex: 'configValue', search: false,
|
||||
render: (_, r) => r.isEncrypted === 'Y' ? <Tag color="red">암호화 (***)</Tag> : <span>{r.configValue}</span>,
|
||||
},
|
||||
{ title: '타입', dataIndex: 'valueType', width: 90, search: false },
|
||||
{ title: '설명', dataIndex: 'configDesc', ellipsis: true, search: false },
|
||||
{
|
||||
title: '수정 가능', dataIndex: 'isEditable', width: 100, align: 'center', search: false,
|
||||
render: (_, r) => r.isEditable === 'Y' ? <Tag color="success">가능</Tag> : <Tag>잠김</Tag>,
|
||||
},
|
||||
{ title: '수정일시', dataIndex: 'updatedAt', width: 160, search: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="시스템 설정" description="배치 / 보안 / 세금 / 메일 등 키-값 설정">
|
||||
<ProTable<SystemConfigResp>
|
||||
rowKey="configKey"
|
||||
columns={columns}
|
||||
scroll={{ x: 1200 }}
|
||||
request={async (params) => {
|
||||
const list = await configApi.list((params as any).configGroup);
|
||||
return { data: list, total: list.length, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t}건` }}
|
||||
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
||||
options={{ density: false, fullScreen: true, reload: true, setting: true }}
|
||||
dateFormatter="string"
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { useState } from 'react';
|
||||
import { Tabs, Tag } from 'antd';
|
||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import { logApi, ApiAccessLogResp, DataChangeLogResp, LoginLogResp } from '@/api/log';
|
||||
|
||||
export default function SystemLog() {
|
||||
const [tab, setTab] = useState('login');
|
||||
|
||||
return (
|
||||
<PageContainer title="시스템 로그" description="로그인 / API 호출 / 데이터 변경 이력">
|
||||
<Tabs
|
||||
activeKey={tab}
|
||||
onChange={setTab}
|
||||
items={[
|
||||
{ key: 'login', label: '로그인 이력', children: <LoginTable /> },
|
||||
{ key: 'api', label: 'API 호출', children: <ApiTable /> },
|
||||
{ key: 'change', label: '변경 이력', children: <ChangeTable /> },
|
||||
]}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function LoginTable() {
|
||||
const columns: ProColumns<LoginLogResp>[] = [
|
||||
{ title: '로그인일시', dataIndex: 'loginAt', width: 170, valueType: 'dateTime' },
|
||||
{
|
||||
title: '구분', dataIndex: 'loginType', width: 90, valueType: 'select',
|
||||
valueEnum: { LOGIN: { text: '로그인' }, LOGOUT: { text: '로그아웃' }, FAIL: { text: '실패' } },
|
||||
render: (_, r) => {
|
||||
const c = r.loginType === 'LOGIN' ? 'success' : r.loginType === 'FAIL' ? 'error' : 'default';
|
||||
return <Tag color={c}>{r.loginType}</Tag>;
|
||||
},
|
||||
},
|
||||
{ title: 'ID', dataIndex: 'username', width: 120 },
|
||||
{ title: 'IP', dataIndex: 'ipAddress', width: 130, search: false },
|
||||
{ title: 'User-Agent', dataIndex: 'userAgent', ellipsis: true, search: false },
|
||||
{ title: '실패사유', dataIndex: 'failReason', width: 200, search: false },
|
||||
];
|
||||
return (
|
||||
<ProTable<LoginLogResp>
|
||||
rowKey="logId" columns={columns} dateFormatter="string" scroll={{ x: 1100 }}
|
||||
request={async (params) => {
|
||||
const { current, pageSize, ...rest } = params as any;
|
||||
const res = await logApi.login({ ...rest, pageNum: current, pageSize });
|
||||
return { data: res.list, total: res.total, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t.toLocaleString()}건` }}
|
||||
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ApiTable() {
|
||||
const columns: ProColumns<ApiAccessLogResp>[] = [
|
||||
{ title: '시각', dataIndex: 'createdAt', width: 170, valueType: 'dateTime' },
|
||||
{ title: 'Method', dataIndex: 'requestMethod', width: 80, search: false,
|
||||
render: (_, r) => <Tag color="blue">{r.requestMethod}</Tag> },
|
||||
{ title: 'URL', dataIndex: 'requestUrl', ellipsis: true, search: false },
|
||||
{ title: 'Status', dataIndex: 'responseStatus', width: 80, align: 'right', search: false,
|
||||
render: (_, r) => {
|
||||
const s = r.responseStatus ?? 0;
|
||||
const c = s >= 500 ? 'error' : s >= 400 ? 'warning' : s >= 300 ? 'processing' : 'success';
|
||||
return <Tag color={c}>{s}</Tag>;
|
||||
} },
|
||||
{ title: 'Time(ms)', dataIndex: 'responseTime', width: 90, align: 'right', search: false },
|
||||
{ title: 'User', dataIndex: 'username', width: 110 },
|
||||
{ title: 'IP', dataIndex: 'ipAddress', width: 130, search: false },
|
||||
];
|
||||
return (
|
||||
<ProTable<ApiAccessLogResp>
|
||||
rowKey="logId" columns={columns} dateFormatter="string" scroll={{ x: 1200 }}
|
||||
request={async (params) => {
|
||||
const { current, pageSize, ...rest } = params as any;
|
||||
const res = await logApi.api({ ...rest, pageNum: current, pageSize });
|
||||
return { data: res.list, total: res.total, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t.toLocaleString()}건` }}
|
||||
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ChangeTable() {
|
||||
const columns: ProColumns<DataChangeLogResp>[] = [
|
||||
{ title: '시각', dataIndex: 'createdAt', width: 170, valueType: 'dateTime' },
|
||||
{ title: '테이블', dataIndex: 'tableName', width: 150 },
|
||||
{ title: 'Action', dataIndex: 'actionType', width: 90, search: false,
|
||||
render: (_, r) => {
|
||||
const c = r.actionType === 'CREATE' ? 'success' : r.actionType === 'DELETE' ? 'error' : 'processing';
|
||||
return <Tag color={c}>{r.actionType}</Tag>;
|
||||
} },
|
||||
{ title: 'Record ID', dataIndex: 'recordId', width: 100, search: false },
|
||||
{ title: '메뉴', dataIndex: 'menuCode', width: 130, search: false },
|
||||
{ title: '사용자', dataIndex: 'username', width: 110 },
|
||||
{ title: '사유', dataIndex: 'changeReason', ellipsis: true, search: false },
|
||||
];
|
||||
return (
|
||||
<ProTable<DataChangeLogResp>
|
||||
rowKey="logId" columns={columns} dateFormatter="string" scroll={{ x: 1100 }}
|
||||
request={async (params) => {
|
||||
const { current, pageSize, ...rest } = params as any;
|
||||
const res = await logApi.change({ ...rest, pageNum: current, pageSize });
|
||||
return { data: res.list, total: res.total, success: true };
|
||||
}}
|
||||
pagination={{ pageSize: 50, showTotal: (t) => `총 ${t.toLocaleString()}건` }}
|
||||
search={{ labelWidth: 'auto', searchText: '검색', resetText: '초기화', collapsed: false, collapseRender: false }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user