feat(CEODashboard): Phase 3 - 카드/가지급금 관리 모달 API 연동
Phase 3: Modal Component Integration (cm1-cm4) ### 새 파일 - cardManagementConfigTransformers.ts: API 응답을 모달 설정으로 변환 - transformCm1ModalConfig: 카드 사용액 상세 - transformCm2ModalConfig: 가지급금 상세 - transformCm3ModalConfig: 법인세 시뮬레이션 - transformCm4ModalConfig: 종합소득세 시뮬레이션 - 유틸리티: formatKoreanCurrency, calculateChangeRate, formatPercentage ### 수정 파일 - cardManagementConfigs.ts: - getCardManagementModalConfigWithData() 추가 - API 데이터 우선, 없을 시 fallback 설정 사용 - useCardManagementModals.ts: - fetchModalData()가 데이터 직접 반환하도록 수정 - CardManagementModalData 타입 추가 - CEODashboard.tsx: - useCardManagementModals 훅 연동 - handleCardManagementCardClick에서 API 데이터 사용
This commit is contained in:
@@ -25,10 +25,13 @@ import { DEFAULT_DASHBOARD_SETTINGS } from './types';
|
||||
import { ScheduleDetailModal, DetailModal } from './modals';
|
||||
import { DashboardSettingsDialog } from './dialogs/DashboardSettingsDialog';
|
||||
import { mockData } from './mockData';
|
||||
import { useCEODashboard, useTodayIssue, useCalendar, useVat, useEntertainment, useWelfare, useWelfareDetail } from '@/hooks/useCEODashboard';
|
||||
import { useCEODashboard, useTodayIssue, useCalendar, useVat, useEntertainment, useWelfare, useWelfareDetail, useMonthlyExpenseDetail } from '@/hooks/useCEODashboard';
|
||||
import { useCardManagementModals, type CardManagementCardId } from '@/hooks/useCardManagementModals';
|
||||
import type { MonthlyExpenseCardId } from '@/hooks/useCEODashboard';
|
||||
import {
|
||||
getMonthlyExpenseModalConfig,
|
||||
getCardManagementModalConfig,
|
||||
getCardManagementModalConfigWithData,
|
||||
getEntertainmentModalConfig,
|
||||
getWelfareModalConfig,
|
||||
getVatModalConfig,
|
||||
@@ -57,6 +60,9 @@ export function CEODashboard() {
|
||||
// Welfare API Hook (Phase 2)
|
||||
const welfareData = useWelfare();
|
||||
|
||||
// Card Management Modal API Hook (Phase 3)
|
||||
const cardManagementModals = useCardManagementModals();
|
||||
|
||||
// 전체 로딩 상태 (하나라도 로딩 중이면 스켈레톤 표시)
|
||||
const isLoading = useMemo(() => {
|
||||
return (
|
||||
@@ -105,6 +111,9 @@ export function CEODashboard() {
|
||||
calculationType: dashboardSettings.welfare.calculationType,
|
||||
});
|
||||
|
||||
// MonthlyExpenseDetail Hook (당월 예상 지출 모달용 상세 API)
|
||||
const monthlyExpenseDetailData = useMonthlyExpenseDetail();
|
||||
|
||||
// 상세 모달 상태
|
||||
const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);
|
||||
const [detailModalConfig, setDetailModalConfig] = useState<DetailModalConfig | null>(null);
|
||||
@@ -152,13 +161,17 @@ export function CEODashboard() {
|
||||
}, []);
|
||||
|
||||
// 당월 예상 지출 카드 클릭 (개별 카드 클릭 시 상세 모달)
|
||||
const handleMonthlyExpenseCardClick = useCallback((cardId: string) => {
|
||||
const config = getMonthlyExpenseModalConfig(cardId);
|
||||
const handleMonthlyExpenseCardClick = useCallback(async (cardId: string) => {
|
||||
// 1. 먼저 API에서 데이터 fetch 시도
|
||||
const apiConfig = await monthlyExpenseDetailData.fetchData(cardId as MonthlyExpenseCardId);
|
||||
|
||||
// 2. API 데이터가 있으면 사용, 없으면 fallback config 사용
|
||||
const config = apiConfig ?? getMonthlyExpenseModalConfig(cardId);
|
||||
if (config) {
|
||||
setDetailModalConfig(config);
|
||||
setIsDetailModalOpen(true);
|
||||
}
|
||||
}, []);
|
||||
}, [monthlyExpenseDetailData]);
|
||||
|
||||
// 당월 예상 지출 클릭 (deprecated - 개별 카드 클릭으로 대체)
|
||||
const handleMonthlyExpenseClick = useCallback(() => {
|
||||
@@ -166,13 +179,18 @@ export function CEODashboard() {
|
||||
}, []);
|
||||
|
||||
// 카드/가지급금 관리 카드 클릭 (개별 카드 클릭 시 상세 모달)
|
||||
const handleCardManagementCardClick = useCallback((cardId: string) => {
|
||||
const config = getCardManagementModalConfig(cardId);
|
||||
const handleCardManagementCardClick = useCallback(async (cardId: string) => {
|
||||
// 1. API에서 데이터 fetch (데이터 직접 반환)
|
||||
const modalData = await cardManagementModals.fetchModalData(cardId as CardManagementCardId);
|
||||
|
||||
// 2. API 데이터로 config 생성 (데이터 없으면 fallback)
|
||||
const config = getCardManagementModalConfigWithData(cardId, modalData);
|
||||
|
||||
if (config) {
|
||||
setDetailModalConfig(config);
|
||||
setIsDetailModalOpen(true);
|
||||
}
|
||||
}, []);
|
||||
}, [cardManagementModals]);
|
||||
|
||||
// 접대비 현황 카드 클릭 (개별 카드 클릭 시 상세 모달)
|
||||
const handleEntertainmentCardClick = useCallback((cardId: string) => {
|
||||
|
||||
@@ -0,0 +1,477 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* Card Management Modal Config Transformers
|
||||
*
|
||||
* API 응답 데이터를 DetailModalConfig로 변환하는 함수들
|
||||
*
|
||||
* cm1: CardDashboardDetailApiResponse → DetailModalConfig
|
||||
* cm2: LoanDashboardApiResponse → DetailModalConfig
|
||||
* cm3: TaxSimulationApiResponse → DetailModalConfig (법인세)
|
||||
* cm4: TaxSimulationApiResponse → DetailModalConfig (종합소득세)
|
||||
*/
|
||||
|
||||
import type { DetailModalConfig } from '../types';
|
||||
import type {
|
||||
CardDashboardDetailApiResponse,
|
||||
LoanDashboardApiResponse,
|
||||
TaxSimulationApiResponse,
|
||||
} from '@/lib/api/dashboard/types';
|
||||
|
||||
// ============================================
|
||||
// 유틸리티 함수
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 숫자를 한국식 단위로 포맷팅 (억, 만)
|
||||
*/
|
||||
function formatKoreanCurrency(value: number): string {
|
||||
if (value >= 100000000) {
|
||||
const billions = value / 100000000;
|
||||
return `${billions.toFixed(1)}억원`;
|
||||
}
|
||||
if (value >= 10000) {
|
||||
const thousands = value / 10000;
|
||||
return `${thousands.toFixed(0)}만원`;
|
||||
}
|
||||
return `${value.toLocaleString()}원`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 전월 대비 증감률 계산
|
||||
*/
|
||||
function calculateChangeRate(current: number, previous: number): number {
|
||||
if (previous === 0) return current > 0 ? 100 : 0;
|
||||
return ((current - previous) / previous) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 퍼센트 값을 포맷팅
|
||||
*/
|
||||
function formatPercentage(value: number, showSign = true): string {
|
||||
const sign = showSign && value > 0 ? '+' : '';
|
||||
return `${sign}${value.toFixed(1)}%`;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// cm1: 카드 사용 상세 모달 변환기
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 카드 대시보드 API 응답을 cm1 모달 설정으로 변환
|
||||
*/
|
||||
export function transformCm1ModalConfig(
|
||||
data: CardDashboardDetailApiResponse
|
||||
): DetailModalConfig {
|
||||
const { summary, monthly_trend, by_user, items } = data;
|
||||
|
||||
// 전월 대비 증감률 계산
|
||||
const changeRate = calculateChangeRate(
|
||||
summary.current_month_total,
|
||||
summary.previous_month_total
|
||||
);
|
||||
|
||||
// 미정리 건수 계산 (usage_type이 '미설정'인 항목)
|
||||
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) => ({
|
||||
name: user.user_name,
|
||||
value: user.amount,
|
||||
percentage: totalAmount > 0 ? Math.round((user.amount / totalAmount) * 100) : 0,
|
||||
color: user.color,
|
||||
}));
|
||||
|
||||
// 사용자 필터 옵션 동적 생성
|
||||
const userFilterOptions = [
|
||||
{ value: 'all', label: '전체' },
|
||||
...by_user.map((user) => ({
|
||||
value: user.user_name,
|
||||
label: user.user_name,
|
||||
})),
|
||||
];
|
||||
|
||||
// 테이블 데이터 매핑
|
||||
const tableData = items.map((item) => ({
|
||||
cardName: item.card_name,
|
||||
user: item.user_name,
|
||||
date: item.transaction_date,
|
||||
store: item.merchant_name,
|
||||
amount: item.amount,
|
||||
usageType: item.usage_type || '미설정',
|
||||
}));
|
||||
|
||||
return {
|
||||
title: '카드 사용 상세',
|
||||
summaryCards: [
|
||||
{ label: '당월 카드 사용', value: summary.current_month_total, unit: '원' },
|
||||
{
|
||||
label: '전월 대비',
|
||||
value: formatPercentage(changeRate),
|
||||
isComparison: true,
|
||||
isPositive: changeRate >= 0,
|
||||
},
|
||||
{ label: '미정리 건수', value: `${unprocessedCount}건` },
|
||||
],
|
||||
barChart: {
|
||||
title: '월별 카드 사용 추이',
|
||||
data: monthly_trend.map((trend) => ({
|
||||
name: trend.label,
|
||||
value: trend.amount,
|
||||
})),
|
||||
dataKey: 'value',
|
||||
xAxisKey: 'name',
|
||||
color: '#60A5FA',
|
||||
},
|
||||
pieChart: {
|
||||
title: '사용자별 카드 사용 비율',
|
||||
data: pieChartData,
|
||||
},
|
||||
table: {
|
||||
title: '카드 사용 내역',
|
||||
columns: [
|
||||
{ key: 'no', label: 'No.', align: 'center' },
|
||||
{ key: 'cardName', label: '카드명', align: 'left' },
|
||||
{ key: 'user', label: '사용자', align: 'center' },
|
||||
{ key: 'date', label: '사용일시', align: 'center', format: 'date' },
|
||||
{ key: 'store', label: '가맹점명', align: 'left' },
|
||||
{ key: 'amount', label: '사용금액', align: 'right', format: 'currency' },
|
||||
{ key: 'usageType', label: '사용유형', align: 'center', highlightValue: '미설정' },
|
||||
],
|
||||
data: tableData,
|
||||
filters: [
|
||||
{
|
||||
key: 'user',
|
||||
options: userFilterOptions,
|
||||
defaultValue: 'all',
|
||||
},
|
||||
{
|
||||
key: 'usageType',
|
||||
options: [
|
||||
{ value: 'all', label: '전체' },
|
||||
{ value: '미설정', label: '미설정' },
|
||||
{ value: '복리후생비', label: '복리후생비' },
|
||||
{ value: '접대비', label: '접대비' },
|
||||
{ value: '소모품비', label: '소모품비' },
|
||||
{ value: '교통비', label: '교통비' },
|
||||
],
|
||||
defaultValue: 'all',
|
||||
},
|
||||
{
|
||||
key: 'sortOrder',
|
||||
options: [
|
||||
{ value: 'latest', label: '최신순' },
|
||||
{ value: 'oldest', label: '등록순' },
|
||||
{ value: 'amountDesc', label: '금액 높은순' },
|
||||
{ value: 'amountAsc', label: '금액 낮은순' },
|
||||
],
|
||||
defaultValue: 'latest',
|
||||
},
|
||||
],
|
||||
showTotal: true,
|
||||
totalLabel: '합계',
|
||||
totalValue: summary.current_month_total,
|
||||
totalColumnKey: 'amount',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// cm2: 가지급금 상세 모달 변환기
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 가지급금 대시보드 API 응답을 cm2 모달 설정으로 변환
|
||||
*/
|
||||
export function transformCm2ModalConfig(
|
||||
data: LoanDashboardApiResponse
|
||||
): DetailModalConfig {
|
||||
const { summary, items } = data;
|
||||
|
||||
// 테이블 데이터 매핑
|
||||
const tableData = items.map((item) => ({
|
||||
date: item.loan_date,
|
||||
target: item.user_name,
|
||||
category: '-', // API에서 별도 필드 없음
|
||||
amount: item.amount,
|
||||
status: item.status_label || item.status,
|
||||
content: item.description,
|
||||
}));
|
||||
|
||||
// 대상 필터 옵션 동적 생성
|
||||
const uniqueTargets = [...new Set(items.map((item) => item.user_name))];
|
||||
const targetFilterOptions = [
|
||||
{ value: 'all', label: '전체' },
|
||||
...uniqueTargets.map((target) => ({
|
||||
value: target,
|
||||
label: target,
|
||||
})),
|
||||
];
|
||||
|
||||
return {
|
||||
title: '가지급금 상세',
|
||||
summaryCards: [
|
||||
{ label: '가지급금', value: formatKoreanCurrency(summary.total_outstanding) },
|
||||
{ label: '인정이자 4.6%', value: summary.recognized_interest, unit: '원' },
|
||||
{ label: '미정정', value: `${summary.pending_count}건` },
|
||||
],
|
||||
table: {
|
||||
title: '가지급금 관련 내역',
|
||||
columns: [
|
||||
{ key: 'no', label: 'No.', align: 'center' },
|
||||
{ key: 'date', label: '발생일시', align: 'center' },
|
||||
{ key: 'target', label: '대상', align: 'center' },
|
||||
{ key: 'category', label: '구분', align: 'center' },
|
||||
{ key: 'amount', label: '금액', align: 'right', format: 'currency' },
|
||||
{ key: 'status', label: '상태', align: 'center', highlightValue: '미정정' },
|
||||
{ key: 'content', label: '내용', align: 'left' },
|
||||
],
|
||||
data: tableData,
|
||||
filters: [
|
||||
{
|
||||
key: 'target',
|
||||
options: targetFilterOptions,
|
||||
defaultValue: 'all',
|
||||
},
|
||||
{
|
||||
key: 'category',
|
||||
options: [
|
||||
{ value: 'all', label: '전체' },
|
||||
{ value: '카드명', label: '카드명' },
|
||||
{ value: '계좌명', label: '계좌명' },
|
||||
],
|
||||
defaultValue: 'all',
|
||||
},
|
||||
{
|
||||
key: 'sortOrder',
|
||||
options: [
|
||||
{ value: 'latest', label: '최신순' },
|
||||
{ value: 'oldest', label: '등록순' },
|
||||
{ value: 'amountDesc', label: '금액 높은순' },
|
||||
{ value: 'amountAsc', label: '금액 낮은순' },
|
||||
],
|
||||
defaultValue: 'latest',
|
||||
},
|
||||
],
|
||||
showTotal: true,
|
||||
totalLabel: '합계',
|
||||
totalValue: summary.total_outstanding,
|
||||
totalColumnKey: 'amount',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// cm3: 법인세 예상 가중 상세 모달 변환기
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 세금 시뮬레이션 API 응답을 cm3 (법인세) 모달 설정으로 변환
|
||||
*/
|
||||
export function transformCm3ModalConfig(
|
||||
data: TaxSimulationApiResponse
|
||||
): DetailModalConfig {
|
||||
const { loan_summary, corporate_tax } = data;
|
||||
|
||||
return {
|
||||
title: '법인세 예상 가중 상세',
|
||||
summaryCards: [
|
||||
{ label: '법인세 예상 증가', value: corporate_tax.difference, unit: '원' },
|
||||
{ label: '인정 이자', value: loan_summary.recognized_interest, unit: '원' },
|
||||
{ label: '가지급금', value: formatKoreanCurrency(loan_summary.total_outstanding) },
|
||||
{
|
||||
label: `인정이자 ${(loan_summary.interest_rate * 100).toFixed(1)}%`,
|
||||
value: loan_summary.recognized_interest,
|
||||
unit: '원',
|
||||
},
|
||||
],
|
||||
comparisonSection: {
|
||||
leftBox: {
|
||||
title: '없을때 법인세',
|
||||
items: [
|
||||
{
|
||||
label: '과세표준',
|
||||
value: formatKoreanCurrency(corporate_tax.without_loan.taxable_income),
|
||||
},
|
||||
{ label: '법인세', value: corporate_tax.without_loan.tax_amount, unit: '원' },
|
||||
],
|
||||
borderColor: 'orange',
|
||||
},
|
||||
rightBox: {
|
||||
title: '있을때 법인세',
|
||||
items: [
|
||||
{
|
||||
label: '과세표준',
|
||||
value: formatKoreanCurrency(corporate_tax.with_loan.taxable_income),
|
||||
},
|
||||
{ label: '법인세', value: corporate_tax.with_loan.tax_amount, unit: '원' },
|
||||
],
|
||||
borderColor: 'blue',
|
||||
},
|
||||
vsLabel: '법인세 예상 증가',
|
||||
vsValue: corporate_tax.difference,
|
||||
vsSubLabel: corporate_tax.rate_info,
|
||||
},
|
||||
referenceTable: {
|
||||
title: '법인세 과세표준 (2024년 기준)',
|
||||
columns: [
|
||||
{ key: 'bracket', label: '과세표준', align: 'left' },
|
||||
{ key: 'rate', label: '세율', align: 'center' },
|
||||
{ key: 'formula', label: '계산식', align: 'left' },
|
||||
],
|
||||
data: [
|
||||
{ bracket: '2억원 이하', rate: '9%', formula: '과세표준 × 9%' },
|
||||
{
|
||||
bracket: '2억원 초과 ~ 200억원 이하',
|
||||
rate: '19%',
|
||||
formula: '1,800만원 + (2억원 초과분 × 19%)',
|
||||
},
|
||||
{
|
||||
bracket: '200억원 초과 ~ 3,000억원 이하',
|
||||
rate: '21%',
|
||||
formula: '37.62억원 + (200억원 초과분 × 21%)',
|
||||
},
|
||||
{
|
||||
bracket: '3,000억원 초과',
|
||||
rate: '24%',
|
||||
formula: '625.62억원 + (3,000억원 초과분 × 24%)',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// cm4: 대표자 종합소득세 예상 가중 상세 모달 변환기
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 세금 시뮬레이션 API 응답을 cm4 (종합소득세) 모달 설정으로 변환
|
||||
*/
|
||||
export function transformCm4ModalConfig(
|
||||
data: TaxSimulationApiResponse
|
||||
): DetailModalConfig {
|
||||
const { loan_summary, income_tax } = data;
|
||||
|
||||
// 세금 증가율 계산
|
||||
const taxIncreaseRate =
|
||||
income_tax.without_loan.tax_amount > 0
|
||||
? ((income_tax.difference / income_tax.without_loan.tax_amount) * 100).toFixed(1)
|
||||
: '0';
|
||||
|
||||
return {
|
||||
title: '대표자 종합소득세 예상 가중 상세',
|
||||
summaryCards: [
|
||||
{ label: '대표자 종합세 예상 가중', value: income_tax.difference, unit: '원' },
|
||||
{
|
||||
label: '추가 세금',
|
||||
value: `+${taxIncreaseRate}%`,
|
||||
isComparison: true,
|
||||
isPositive: false,
|
||||
},
|
||||
{ label: '가지급금', value: formatKoreanCurrency(loan_summary.total_outstanding) },
|
||||
{
|
||||
label: `인정이자 ${(loan_summary.interest_rate * 100).toFixed(1)}%`,
|
||||
value: loan_summary.recognized_interest,
|
||||
unit: '원',
|
||||
},
|
||||
],
|
||||
comparisonSection: {
|
||||
leftBox: {
|
||||
title: '가지급금 인정이자가 반영된 종합소득세',
|
||||
items: [
|
||||
{
|
||||
label: '현재 예상 과세표준 (근로소득+상여)',
|
||||
value: income_tax.with_loan.taxable_income,
|
||||
unit: '원',
|
||||
},
|
||||
{ label: '현재 적용 세율', value: income_tax.with_loan.tax_rate },
|
||||
{ label: '현재 예상 세액', value: income_tax.with_loan.tax_amount, unit: '원' },
|
||||
],
|
||||
borderColor: 'orange',
|
||||
},
|
||||
rightBox: {
|
||||
title: '가지급금 인정이자가 정리된 종합소득세',
|
||||
items: [
|
||||
{
|
||||
label: '가지급금 정리 시 예상 과세표준 (근로소득+상여)',
|
||||
value: income_tax.without_loan.taxable_income,
|
||||
unit: '원',
|
||||
},
|
||||
{ label: '가지급금 정리 시 적용 세율', value: income_tax.without_loan.tax_rate },
|
||||
{
|
||||
label: '가지급금 정리 시 예상 세액',
|
||||
value: income_tax.without_loan.tax_amount,
|
||||
unit: '원',
|
||||
},
|
||||
],
|
||||
borderColor: 'blue',
|
||||
},
|
||||
vsLabel: '종합소득세 예상 절감',
|
||||
vsValue: income_tax.difference,
|
||||
vsSubLabel: `감소 세금 -${taxIncreaseRate}%`,
|
||||
vsBreakdown: [
|
||||
{ label: '종합소득세', value: -income_tax.breakdown.income_tax, unit: '원' },
|
||||
{ label: '지방소득세', value: -income_tax.breakdown.local_tax, unit: '원' },
|
||||
{ label: '4대 보험', value: -income_tax.breakdown.insurance, unit: '원' },
|
||||
],
|
||||
},
|
||||
referenceTable: {
|
||||
title: '종합소득세 과세표준 (2024년 기준)',
|
||||
columns: [
|
||||
{ key: 'bracket', label: '과세표준', align: 'left' },
|
||||
{ key: 'rate', label: '세율', align: 'center' },
|
||||
{ key: 'deduction', label: '누진공제', align: 'right' },
|
||||
{ key: 'formula', label: '계산식', align: 'left' },
|
||||
],
|
||||
data: [
|
||||
{ bracket: '1,400만원 이하', rate: '6%', deduction: '-', formula: '과세표준 × 6%' },
|
||||
{
|
||||
bracket: '1,400만원 초과 ~ 5,000만원 이하',
|
||||
rate: '15%',
|
||||
deduction: '126만원',
|
||||
formula: '과세표준 × 15% - 126만원',
|
||||
},
|
||||
{
|
||||
bracket: '5,000만원 초과 ~ 8,800만원 이하',
|
||||
rate: '24%',
|
||||
deduction: '576만원',
|
||||
formula: '과세표준 × 24% - 576만원',
|
||||
},
|
||||
{
|
||||
bracket: '8,800만원 초과 ~ 1.5억원 이하',
|
||||
rate: '35%',
|
||||
deduction: '1,544만원',
|
||||
formula: '과세표준 × 35% - 1,544만원',
|
||||
},
|
||||
{
|
||||
bracket: '1.5억원 초과 ~ 3억원 이하',
|
||||
rate: '38%',
|
||||
deduction: '1,994만원',
|
||||
formula: '과세표준 × 38% - 1,994만원',
|
||||
},
|
||||
{
|
||||
bracket: '3억원 초과 ~ 5억원 이하',
|
||||
rate: '40%',
|
||||
deduction: '2,594만원',
|
||||
formula: '과세표준 × 40% - 2,594만원',
|
||||
},
|
||||
{
|
||||
bracket: '5억원 초과 ~ 10억원 이하',
|
||||
rate: '42%',
|
||||
deduction: '3,594만원',
|
||||
formula: '과세표준 × 42% - 3,594만원',
|
||||
},
|
||||
{
|
||||
bracket: '10억원 초과',
|
||||
rate: '45%',
|
||||
deduction: '6,594만원',
|
||||
formula: '과세표준 × 45% - 6,594만원',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,15 @@
|
||||
import type { DetailModalConfig } from '../types';
|
||||
import type {
|
||||
CardDashboardDetailApiResponse,
|
||||
LoanDashboardApiResponse,
|
||||
TaxSimulationApiResponse,
|
||||
} from '@/lib/api/dashboard/types';
|
||||
import {
|
||||
transformCm1ModalConfig,
|
||||
transformCm2ModalConfig,
|
||||
transformCm3ModalConfig,
|
||||
transformCm4ModalConfig,
|
||||
} from './cardManagementConfigTransformers';
|
||||
|
||||
/**
|
||||
* 카드/가지급금 관리 모달 설정
|
||||
@@ -7,6 +18,64 @@ import type { DetailModalConfig } from '../types';
|
||||
* cm3: 법인세 예상 가중 상세
|
||||
* cm4: 대표자 종합소득세 예상 가중 상세
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// API 데이터 기반 모달 설정 생성
|
||||
// ============================================
|
||||
|
||||
export interface CardManagementModalData {
|
||||
cm1Data?: CardDashboardDetailApiResponse | null;
|
||||
cm2Data?: LoanDashboardApiResponse | null;
|
||||
cm3Data?: TaxSimulationApiResponse | null;
|
||||
cm4Data?: TaxSimulationApiResponse | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 데이터를 사용하여 모달 설정을 동적으로 생성
|
||||
* 데이터가 없는 경우 fallback 설정 사용
|
||||
*/
|
||||
export function getCardManagementModalConfigWithData(
|
||||
cardId: string,
|
||||
data: CardManagementModalData
|
||||
): DetailModalConfig | null {
|
||||
switch (cardId) {
|
||||
case 'cm1':
|
||||
if (data.cm1Data) {
|
||||
return transformCm1ModalConfig(data.cm1Data);
|
||||
}
|
||||
return getCardManagementModalConfig(cardId);
|
||||
|
||||
case 'cm2':
|
||||
if (data.cm2Data) {
|
||||
return transformCm2ModalConfig(data.cm2Data);
|
||||
}
|
||||
return getCardManagementModalConfig(cardId);
|
||||
|
||||
case 'cm3':
|
||||
if (data.cm3Data) {
|
||||
return transformCm3ModalConfig(data.cm3Data);
|
||||
}
|
||||
return getCardManagementModalConfig(cardId);
|
||||
|
||||
case 'cm4':
|
||||
if (data.cm4Data) {
|
||||
return transformCm4ModalConfig(data.cm4Data);
|
||||
}
|
||||
return getCardManagementModalConfig(cardId);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Fallback 모달 설정 (API 데이터 없을 때 사용)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Fallback: 정적 목업 데이터 기반 모달 설정
|
||||
* API 데이터가 없을 때 사용
|
||||
*/
|
||||
export function getCardManagementModalConfig(cardId: string): DetailModalConfig | null {
|
||||
const configs: Record<string, DetailModalConfig> = {
|
||||
cm1: {
|
||||
@@ -266,4 +335,4 @@ export function getCardManagementModalConfig(cardId: string): DetailModalConfig
|
||||
};
|
||||
|
||||
return configs[cardId] || null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { getMonthlyExpenseModalConfig } from './monthlyExpenseConfigs';
|
||||
export { getCardManagementModalConfig } from './cardManagementConfigs';
|
||||
export { getCardManagementModalConfig, getCardManagementModalConfigWithData } from './cardManagementConfigs';
|
||||
export { getEntertainmentModalConfig } from './entertainmentConfigs';
|
||||
export { getWelfareModalConfig } from './welfareConfigs';
|
||||
export { getVatModalConfig } from './vatConfigs';
|
||||
@@ -30,6 +30,14 @@ import type {
|
||||
/** 카드 ID 타입 */
|
||||
export type CardManagementCardId = 'cm1' | 'cm2' | 'cm3' | 'cm4';
|
||||
|
||||
/** 모달 데이터 반환 타입 */
|
||||
export interface CardManagementModalData {
|
||||
cm1Data?: CardDashboardDetailApiResponse | null;
|
||||
cm2Data?: LoanDashboardApiResponse | null;
|
||||
cm3Data?: TaxSimulationApiResponse | null;
|
||||
cm4Data?: TaxSimulationApiResponse | null;
|
||||
}
|
||||
|
||||
/** Hook 반환 타입 */
|
||||
export interface UseCardManagementModalsReturn {
|
||||
/** cm1: 카드 사용액 상세 데이터 */
|
||||
@@ -44,8 +52,8 @@ export interface UseCardManagementModalsReturn {
|
||||
loading: boolean;
|
||||
/** 에러 메시지 */
|
||||
error: string | null;
|
||||
/** 특정 카드의 모달 데이터 조회 */
|
||||
fetchModalData: (cardId: CardManagementCardId) => Promise<void>;
|
||||
/** 특정 카드의 모달 데이터 조회 - 데이터 직접 반환 */
|
||||
fetchModalData: (cardId: CardManagementCardId) => Promise<CardManagementModalData>;
|
||||
/** 모든 카드 데이터 조회 */
|
||||
fetchAllData: () => Promise<void>;
|
||||
/** 데이터 초기화 */
|
||||
@@ -78,12 +86,14 @@ export function useCardManagementModals(): UseCardManagementModalsReturn {
|
||||
|
||||
/**
|
||||
* cm1: 카드 사용액 상세 데이터 조회
|
||||
* @returns 조회된 데이터 (실패 시 null)
|
||||
*/
|
||||
const fetchCm1Data = useCallback(async () => {
|
||||
const fetchCm1Data = useCallback(async (): Promise<CardDashboardDetailApiResponse | null> => {
|
||||
try {
|
||||
const response = await fetchCardTransactionDashboard();
|
||||
if (response.success) {
|
||||
if (response.success && response.data) {
|
||||
setCm1Data(response.data);
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(response.message || '카드 거래 데이터 조회 실패');
|
||||
}
|
||||
@@ -96,12 +106,14 @@ export function useCardManagementModals(): UseCardManagementModalsReturn {
|
||||
|
||||
/**
|
||||
* cm2: 가지급금 상세 데이터 조회
|
||||
* @returns 조회된 데이터 (실패 시 null)
|
||||
*/
|
||||
const fetchCm2Data = useCallback(async () => {
|
||||
const fetchCm2Data = useCallback(async (): Promise<LoanDashboardApiResponse | null> => {
|
||||
try {
|
||||
const response = await fetchLoanDashboard();
|
||||
if (response.success) {
|
||||
if (response.success && response.data) {
|
||||
setCm2Data(response.data);
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(response.message || '가지급금 데이터 조회 실패');
|
||||
}
|
||||
@@ -115,14 +127,16 @@ export function useCardManagementModals(): UseCardManagementModalsReturn {
|
||||
/**
|
||||
* cm3 & cm4: 세금 시뮬레이션 데이터 조회
|
||||
* cm3은 법인세 (corporate_tax), cm4는 소득세 (income_tax) 사용
|
||||
* @returns 조회된 데이터 (실패 시 null)
|
||||
*/
|
||||
const fetchTaxData = useCallback(async () => {
|
||||
const fetchTaxData = useCallback(async (): Promise<TaxSimulationApiResponse | null> => {
|
||||
try {
|
||||
const response = await fetchTaxSimulation();
|
||||
if (response.success) {
|
||||
if (response.success && response.data) {
|
||||
// cm3, cm4 모두 같은 데이터 소스 사용 (표시만 다름)
|
||||
setCm3Data(response.data);
|
||||
setCm4Data(response.data);
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(response.message || '세금 시뮬레이션 데이터 조회 실패');
|
||||
}
|
||||
@@ -135,25 +149,31 @@ export function useCardManagementModals(): UseCardManagementModalsReturn {
|
||||
|
||||
/**
|
||||
* 특정 카드의 모달 데이터 조회
|
||||
* @returns 조회된 모달 데이터 객체 (카드 ID에 해당하는 데이터만 포함)
|
||||
*/
|
||||
const fetchModalData = useCallback(
|
||||
async (cardId: CardManagementCardId) => {
|
||||
async (cardId: CardManagementCardId): Promise<CardManagementModalData> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const result: CardManagementModalData = {};
|
||||
|
||||
try {
|
||||
switch (cardId) {
|
||||
case 'cm1':
|
||||
await fetchCm1Data();
|
||||
result.cm1Data = await fetchCm1Data();
|
||||
break;
|
||||
case 'cm2':
|
||||
await fetchCm2Data();
|
||||
result.cm2Data = await fetchCm2Data();
|
||||
break;
|
||||
case 'cm3':
|
||||
case 'cm4':
|
||||
case 'cm4': {
|
||||
// cm3, cm4는 같은 API 사용
|
||||
await fetchTaxData();
|
||||
const taxData = await fetchTaxData();
|
||||
result.cm3Data = taxData;
|
||||
result.cm4Data = taxData;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`알 수 없는 카드 ID: ${cardId}`);
|
||||
}
|
||||
@@ -163,6 +183,8 @@ export function useCardManagementModals(): UseCardManagementModalsReturn {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
[fetchCm1Data, fetchCm2Data, fetchTaxData]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user