From e098fea558835e97a74f19c783116298ae4ba7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Fri, 23 Jan 2026 13:31:44 +0900 Subject: [PATCH] =?UTF-8?q?fix(WEB):=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EB=AA=A8=EB=8B=AC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - transformers.ts: footer_summary destructuring 누락 수정 - cardManagementConfigTransformers.ts: items, by_user, monthly_trend 방어적 코드 추가 - undefined 배열 접근 시 빈 배열 기본값 적용 - 카드 사용 상세(cm1), 가지급금 상세(cm2) 모달 에러 해결 --- .../cardManagementConfigTransformers.ts | 21 +-- src/lib/api/dashboard/transformers.ts | 132 +++++++++++++++--- 2 files changed, 122 insertions(+), 31 deletions(-) diff --git a/src/components/business/CEODashboard/modalConfigs/cardManagementConfigTransformers.ts b/src/components/business/CEODashboard/modalConfigs/cardManagementConfigTransformers.ts index 287e1a13..37f95caa 100644 --- a/src/components/business/CEODashboard/modalConfigs/cardManagementConfigTransformers.ts +++ b/src/components/business/CEODashboard/modalConfigs/cardManagementConfigTransformers.ts @@ -63,7 +63,7 @@ function formatPercentage(value: number, showSign = true): string { export function transformCm1ModalConfig( data: CardDashboardDetailApiResponse ): DetailModalConfig { - const { summary, monthly_trend, by_user, items } = data; + const { summary, monthly_trend = [], by_user = [], items = [] } = data; // 전월 대비 증감률 계산 const changeRate = calculateChangeRate( @@ -72,13 +72,14 @@ export function transformCm1ModalConfig( ); // 미정리 건수 계산 (usage_type이 '미설정'인 항목) - const unprocessedCount = items.filter( + const unprocessedCount = (items || []).filter( (item) => item.usage_type === '미설정' || !item.usage_type ).length; // 사용자별 비율 계산 - const totalAmount = by_user.reduce((sum, user) => sum + user.amount, 0); - const pieChartData = by_user.map((user) => ({ + const safeByUser = by_user || []; + const totalAmount = safeByUser.reduce((sum, user) => sum + user.amount, 0); + const pieChartData = safeByUser.map((user) => ({ name: user.user_name, value: user.amount, percentage: totalAmount > 0 ? Math.round((user.amount / totalAmount) * 100) : 0, @@ -88,14 +89,14 @@ export function transformCm1ModalConfig( // 사용자 필터 옵션 동적 생성 const userFilterOptions = [ { value: 'all', label: '전체' }, - ...by_user.map((user) => ({ + ...safeByUser.map((user) => ({ value: user.user_name, label: user.user_name, })), ]; // 테이블 데이터 매핑 - const tableData = items.map((item) => ({ + const tableData = (items || []).map((item) => ({ cardName: item.card_name, user: item.user_name, date: item.transaction_date, @@ -118,7 +119,7 @@ export function transformCm1ModalConfig( ], barChart: { title: '월별 카드 사용 추이', - data: monthly_trend.map((trend) => ({ + data: (monthly_trend || []).map((trend) => ({ name: trend.label, value: trend.amount, })), @@ -189,10 +190,10 @@ export function transformCm1ModalConfig( export function transformCm2ModalConfig( data: LoanDashboardApiResponse ): DetailModalConfig { - const { summary, items } = data; + const { summary, items = [] } = data; // 테이블 데이터 매핑 - const tableData = items.map((item) => ({ + const tableData = (items || []).map((item) => ({ date: item.loan_date, target: item.user_name, category: '-', // API에서 별도 필드 없음 @@ -202,7 +203,7 @@ export function transformCm2ModalConfig( })); // 대상 필터 옵션 동적 생성 - const uniqueTargets = [...new Set(items.map((item) => item.user_name))]; + const uniqueTargets = [...new Set((items || []).map((item) => item.user_name))]; const targetFilterOptions = [ { value: 'all', label: '전체' }, ...uniqueTargets.map((target) => ({ diff --git a/src/lib/api/dashboard/transformers.ts b/src/lib/api/dashboard/transformers.ts index 7d000159..f00882cf 100644 --- a/src/lib/api/dashboard/transformers.ts +++ b/src/lib/api/dashboard/transformers.ts @@ -1189,12 +1189,57 @@ export function transformBillDetailResponse(api: BillDashboardDetailApiResponse) // 16. Expected Expense Dashboard Detail 변환 (me1~me4 통합) // ============================================ -// cardId별 제목 매핑 -const EXPENSE_CARD_TITLES: Record = { - me1: { title: '당월 매입 상세', tableTitle: '매입 내역', summaryLabel: '당월 총 매입' }, - me2: { title: '당월 카드결제 상세', tableTitle: '카드결제 내역', summaryLabel: '당월 총 카드결제' }, - me3: { title: '당월 발행어음 상세', tableTitle: '발행어음 내역', summaryLabel: '당월 총 발행어음' }, - me4: { title: '지출예상 상세', tableTitle: '지출예상 내역', summaryLabel: '당월 총 지출예상' }, +// cardId별 제목 및 차트 설정 매핑 +const EXPENSE_CARD_CONFIG: Record = { + me1: { + title: '당월 매입 상세', + tableTitle: '일별 매입 내역', + summaryLabel: '당월 매입', + barChartTitle: '월별 매입 추이', + pieChartTitle: '거래처별 매입 비율', + hasBarChart: true, + hasPieChart: true, + hasHorizontalBarChart: false, + }, + me2: { + title: '당월 카드 상세', + tableTitle: '일별 카드 사용 내역', + summaryLabel: '당월 카드 사용', + barChartTitle: '월별 카드 사용 추이', + pieChartTitle: '거래처별 카드 사용 비율', + hasBarChart: true, + hasPieChart: true, + hasHorizontalBarChart: false, + }, + me3: { + title: '당월 발행어음 상세', + tableTitle: '일별 발행어음 내역', + summaryLabel: '당월 발행어음 사용', + barChartTitle: '월별 발행어음 추이', + horizontalBarChartTitle: '당월 거래처별 발행어음', + hasBarChart: true, + hasPieChart: false, + hasHorizontalBarChart: true, + }, + me4: { + title: '당월 지출 예상 상세', + tableTitle: '당월 지출 승인 내역서', + summaryLabel: '당월 지출 예상', + barChartTitle: '', + hasBarChart: false, + hasPieChart: false, + hasHorizontalBarChart: false, + }, }; /** @@ -1208,24 +1253,31 @@ export function transformExpectedExpenseDetailResponse( api: ExpectedExpenseDashboardDetailApiResponse, cardId: string = 'me4' ): DetailModalConfig { - const { summary, items } = api; + const { summary, monthly_trend, vendor_distribution, items, footer_summary } = api; const changeRateText = summary.change_rate >= 0 ? `+${summary.change_rate.toFixed(1)}%` : `${summary.change_rate.toFixed(1)}%`; - // cardId별 제목 가져오기 (기본값: me4) - const titles = EXPENSE_CARD_TITLES[cardId] || EXPENSE_CARD_TITLES.me4; + // cardId별 설정 가져오기 (기본값: me4) + const config = EXPENSE_CARD_CONFIG[cardId] || EXPENSE_CARD_CONFIG.me4; - return { - title: titles.title, + // 거래처 필터 옵션 생성 + const vendorOptions = [{ value: 'all', label: '전체' }]; + const uniqueVendors = [...new Set(items.map(item => item.vendor_name).filter(Boolean))]; + uniqueVendors.forEach(vendor => { + vendorOptions.push({ value: vendor, label: vendor }); + }); + + // 결과 객체 생성 + const result: DetailModalConfig = { + title: config.title, summaryCards: [ - { label: titles.summaryLabel, value: summary.total_amount, unit: '원' }, + { label: config.summaryLabel, value: summary.total_amount, unit: '원' }, { label: '전월 대비', value: changeRateText }, { label: '지출 후 예상 잔액', value: summary.remaining_balance, unit: '원' }, ], - // 차트 없음 table: { - title: titles.tableTitle, + title: config.tableTitle, columns: [ { key: 'no', label: 'No.', align: 'center' }, { key: 'paymentDate', label: '결제예정일', align: 'center', format: 'date' }, @@ -1245,9 +1297,7 @@ export function transformExpectedExpenseDetailResponse( filters: [ { key: 'vendor', - options: [ - { value: 'all', label: '전체' }, - ], + options: vendorOptions, defaultValue: 'all', }, { @@ -1265,10 +1315,50 @@ export function transformExpectedExpenseDetailResponse( totalLabel: '합계', totalValue: footer_summary.total_amount, totalColumnKey: 'amount', - footerSummary: { - label: `총 ${footer_summary.item_count}건`, - value: footer_summary.total_amount, - }, + footerSummary: [ + { label: `총 ${footer_summary.item_count}건`, value: footer_summary.total_amount }, + ], }, }; + + // barChart 추가 (me1, me2, me3) + if (config.hasBarChart && monthly_trend && monthly_trend.length > 0) { + result.barChart = { + title: config.barChartTitle, + data: monthly_trend.map(item => ({ + name: item.label, + value: item.amount, + })), + dataKey: 'value', + xAxisKey: 'name', + color: '#60A5FA', + }; + } + + // pieChart 추가 (me1, me2) + if (config.hasPieChart && vendor_distribution && vendor_distribution.length > 0) { + result.pieChart = { + title: config.pieChartTitle || '분포', + data: vendor_distribution.map(item => ({ + name: item.name, + value: item.value, + percentage: item.percentage, + color: item.color, + })), + }; + } + + // horizontalBarChart 추가 (me3) + if (config.hasHorizontalBarChart && vendor_distribution && vendor_distribution.length > 0) { + result.horizontalBarChart = { + title: config.horizontalBarChartTitle || '거래처별 분포', + data: vendor_distribution.map(item => ({ + name: item.name, + value: item.value, + })), + color: '#60A5FA', + }; + } + + return result; } \ No newline at end of file