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,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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user