feat(frontend): Ant Design Pro 패턴 전면 적용
@ant-design/pro-components ^2.8.10 + @ant-design/icons ^6.2 추가. ProLayout (MainLayout): - mix 레이아웃 (사이드바 + 헤더 통합), navTheme=light - 자체 메뉴 트리 렌더링 (children 트리), 동적 메뉴 매핑 - 헤더 actionsRender: 정산월 MonthPicker / 알림 뱃지 - avatarProps: 그라디언트 아바타 + Dropdown 로그아웃 - token: 사이드바/헤더 컬러, pageContainer padding - footerRender: 버전 표기 PageContainer (Pro): - @ant-design/pro-layout 의 PageContainer 사용 - 자동 Breadcrumb / Title / extra 슬롯 ProTable 마이그레이션 (6 페이지): - AgentList: search + valueEnum, request → PageHelper 변환 - ContractList, RecruitLedger, ExceptionLedger, SettleList, PaymentList, UserList - 검색 폼 자동 생성 (valueType: select/dateMonth 등) - toolBarRender: 등록/엑셀 버튼 - options: density/fullScreen/reload/setting Dashboard: - ProCard + StatisticCard 로 KPI 4종 그리드 - 차트 + 배치 진행 + 알림을 ProCard 로 묶음 검증: - tsc -b 통과 - vite build 성공 (2.6MB / gzip 811KB) - 백엔드 76건 단위테스트 영향 없음 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,79 +1,92 @@
|
||||
import { useState } from 'react';
|
||||
import { Table } from 'antd';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useRef } from 'react';
|
||||
import { Space } from 'antd';
|
||||
import { ProTable, type ActionType, type ProColumns } from '@ant-design/pro-components';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import SearchForm from '@/components/common/SearchForm';
|
||||
import CodeBadge from '@/components/common/CodeBadge';
|
||||
import MoneyText from '@/components/common/MoneyText';
|
||||
import PermissionButton from '@/components/common/PermissionButton';
|
||||
import ExcelExportButton from '@/components/common/ExcelExportButton';
|
||||
import { defaultPagination } from '@/utils/tablePagination';
|
||||
import PageContainer from '@/components/common/PageContainer';
|
||||
import { useCommonCodes } from '@/hooks/useCommonCodes';
|
||||
import { agentApi, AgentResp, AgentSearchParam } from '@/api/agent';
|
||||
|
||||
export default function AgentList() {
|
||||
const navigate = useNavigate();
|
||||
const [param, setParam] = useState<AgentSearchParam>({ pageNum: 1, pageSize: 20 });
|
||||
const actionRef = useRef<ActionType>();
|
||||
const { data: statusCodes = [] } = useCommonCodes('AGENT_STATUS');
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['agent', 'list', param],
|
||||
queryFn: () => agentApi.list(param),
|
||||
});
|
||||
const columns: ProColumns<AgentResp>[] = [
|
||||
{
|
||||
title: '설계사명', dataIndex: 'agentName', width: 120, fixed: 'left',
|
||||
render: (_, r) => (
|
||||
<a onClick={() => navigate(`/org/agents/${r.agentId}`)} style={{ fontWeight: 500 }}>
|
||||
{r.agentName}
|
||||
</a>
|
||||
),
|
||||
},
|
||||
{ title: '사번', dataIndex: 'licenseNo', width: 120, search: false },
|
||||
{ title: '소속', dataIndex: 'orgName', width: 200, search: false },
|
||||
{ title: '직급', dataIndex: 'gradeName', width: 100, search: false },
|
||||
{ title: '전화번호', dataIndex: 'phone', width: 140, search: false },
|
||||
{
|
||||
title: '상태', dataIndex: 'status', width: 100, valueType: 'select',
|
||||
valueEnum: Object.fromEntries(statusCodes.map((c) => [c.code, { text: c.codeName }])),
|
||||
render: (_, r) => <CodeBadge groupCode="AGENT_STATUS" value={r.status} />,
|
||||
},
|
||||
{
|
||||
title: '계약수', dataIndex: 'contractCount', width: 90, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.contractCount} />,
|
||||
},
|
||||
{
|
||||
title: '누적수수료', dataIndex: 'totalCommission', width: 130, align: 'right', search: false,
|
||||
render: (_, r) => <MoneyText value={r.totalCommission} />,
|
||||
},
|
||||
{ title: '입사일', dataIndex: 'joinDate', width: 110, search: false },
|
||||
{
|
||||
title: '검색어', dataIndex: 'searchKeyword', hideInTable: true,
|
||||
fieldProps: { placeholder: '설계사명' },
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="설계사 관리"
|
||||
extra={
|
||||
<div>
|
||||
<PermissionButton menuCode="ORG_AGENT" permCode="CREATE" type="primary"
|
||||
onClick={() => navigate('/org/agents/new')} style={{ marginRight: 8 }}>
|
||||
등록
|
||||
</PermissionButton>
|
||||
<ExcelExportButton url="/api/agents/export" params={param} fileName="설계사목록" />
|
||||
</div>
|
||||
}
|
||||
description="조직별 설계사 등록·관리, 누적 수수료 추적"
|
||||
>
|
||||
<SearchForm
|
||||
conditions={[
|
||||
{ type: 'text', name: 'searchKeyword', label: '설계사명' },
|
||||
{ type: 'code', name: 'status', label: '상태', groupCode: 'AGENT_STATUS' },
|
||||
]}
|
||||
onSearch={(v) => setParam({ ...param, ...v, pageNum: 1 })}
|
||||
onReset={() => setParam({ pageNum: 1, pageSize: 20 })}
|
||||
/>
|
||||
|
||||
<Table<AgentResp>
|
||||
<ProTable<AgentResp, AgentSearchParam>
|
||||
actionRef={actionRef}
|
||||
rowKey="agentId"
|
||||
loading={isLoading}
|
||||
dataSource={data?.list}
|
||||
pagination={{
|
||||
...defaultPagination,
|
||||
current: param.pageNum,
|
||||
pageSize: param.pageSize,
|
||||
total: data?.total,
|
||||
onChange: (page, size) => setParam({ ...param, pageNum: page, pageSize: size }),
|
||||
columns={columns}
|
||||
scroll={{ x: 1200 }}
|
||||
request={async (params) => {
|
||||
const { current, pageSize, ...rest } = params as any;
|
||||
const res = await agentApi.list({ ...rest, pageNum: current, pageSize });
|
||||
return { data: res.list, total: res.total, success: true };
|
||||
}}
|
||||
onRow={(r) => ({ onClick: () => navigate(`/org/agents/${r.agentId}`) })}
|
||||
columns={[
|
||||
{ title: '설계사명', dataIndex: 'agentName', width: 120 },
|
||||
{ title: '사번', dataIndex: 'licenseNo', width: 120 },
|
||||
{ title: '소속', dataIndex: 'orgName', width: 200 },
|
||||
{ title: '직급', dataIndex: 'gradeName', width: 100 },
|
||||
{ title: '전화번호', dataIndex: 'phone', width: 140 },
|
||||
{
|
||||
title: '상태', dataIndex: 'status', width: 100,
|
||||
render: (v) => <CodeBadge groupCode="AGENT_STATUS" value={v} />,
|
||||
},
|
||||
{
|
||||
title: '계약수', dataIndex: 'contractCount', align: 'right', width: 90,
|
||||
render: (v) => <MoneyText value={v} />,
|
||||
},
|
||||
{
|
||||
title: '누적수수료', dataIndex: 'totalCommission', align: 'right', width: 130,
|
||||
render: (v) => <MoneyText value={v} />,
|
||||
},
|
||||
{ title: '입사일', dataIndex: 'joinDate', width: 110 },
|
||||
pagination={{
|
||||
pageSize: 20,
|
||||
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={() => [
|
||||
<ExcelExportButton key="export" url="/api/agents/export" fileName="설계사목록" />,
|
||||
<PermissionButton
|
||||
key="create"
|
||||
menuCode="ORG_AGENT" permCode="CREATE" type="primary"
|
||||
onClick={() => navigate('/org/agents/new')}
|
||||
>+ 설계사 등록</PermissionButton>,
|
||||
]}
|
||||
dateFormatter="string"
|
||||
headerTitle={<Space size={6}><span style={{ fontWeight: 600 }}>설계사 목록</span></Space>}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user