'use client'; /** * CEO Dashboard API 연동 Hook * * 각 섹션별 API 호출 및 데이터 변환 담당 * 참조 패턴: useClientList.ts */ import { useState, useCallback, useEffect } from 'react'; import type { DailyReportApiResponse, ReceivablesApiResponse, BadDebtApiResponse, ExpectedExpenseApiResponse, CardTransactionApiResponse, StatusBoardApiResponse, TodayIssueApiResponse, CalendarApiResponse, VatApiResponse, EntertainmentApiResponse, WelfareApiResponse, WelfareDetailApiResponse, ExpectedExpenseDashboardDetailApiResponse, LoanDashboardApiResponse, TaxSimulationApiResponse, } from '@/lib/api/dashboard/types'; import { fetchLoanDashboard, fetchTaxSimulation, } from '@/lib/api/dashboard/endpoints'; import { transformDailyReportResponse, transformReceivableResponse, transformDebtCollectionResponse, transformMonthlyExpenseResponse, transformCardManagementResponse, transformStatusBoardResponse, transformTodayIssueResponse, transformCalendarResponse, transformVatResponse, transformEntertainmentResponse, transformWelfareResponse, transformWelfareDetailResponse, transformExpectedExpenseDetailResponse, transformPurchaseDetailResponse, transformCardDetailResponse, transformBillDetailResponse, } from '@/lib/api/dashboard/transformers'; import type { DailyReportData, ReceivableData, DebtCollectionData, MonthlyExpenseData, CardManagementData, TodayIssueItem, TodayIssueListItem, CalendarScheduleItem, VatData, EntertainmentData, WelfareData, DetailModalConfig, } from '@/components/business/CEODashboard/types'; // ============================================ // 공통 fetch 유틸리티 // ============================================ async function fetchApi(endpoint: string): Promise { const response = await fetch(`/api/proxy/${endpoint}`); if (!response.ok) { throw new Error(`API 오류: ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || '데이터 조회 실패'); } return result.data; } // ============================================ // 1. DailyReport Hook // ============================================ export function useDailyReport() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const apiData = await fetchApi('daily-report/summary'); const transformed = transformDailyReportResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('DailyReport API Error:', err); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 2. Receivable Hook // ============================================ export function useReceivable() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const apiData = await fetchApi('receivables/summary'); const transformed = transformReceivableResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('Receivable API Error:', err); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 3. DebtCollection Hook // ============================================ export function useDebtCollection() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const apiData = await fetchApi('bad-debts/summary'); const transformed = transformDebtCollectionResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('DebtCollection API Error:', err); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 4. MonthlyExpense Hook // ============================================ export function useMonthlyExpense() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const apiData = await fetchApi('expected-expenses/summary'); const transformed = transformMonthlyExpenseResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('MonthlyExpense API Error:', err); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 5. CardManagement Hook // ============================================ export function useCardManagement(fallbackData?: CardManagementData) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); // 3개 API 병렬 호출: 카드거래, 가지급금, 세금 시뮬레이션 const [cardApiData, loanResponse, taxResponse] = await Promise.all([ fetchApi('card-transactions/summary'), fetchLoanDashboard(), fetchTaxSimulation(), ]); // LoanDashboard와 TaxSimulation은 ApiResponse wrapper가 있으므로 data 추출 const loanData = loanResponse.success ? loanResponse.data : null; const taxData = taxResponse.success ? taxResponse.data : null; const transformed = transformCardManagementResponse(cardApiData, loanData, taxData, fallbackData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('CardManagement API Error:', err); } finally { setLoading(false); } }, [fallbackData]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 6. StatusBoard Hook // ============================================ export function useStatusBoard() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const apiData = await fetchApi('status-board/summary'); const transformed = transformStatusBoardResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('StatusBoard API Error:', err); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 7. TodayIssue Hook // ============================================ export interface TodayIssueData { items: TodayIssueListItem[]; totalCount: number; } export function useTodayIssue(limit: number = 30) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const apiData = await fetchApi(`today-issues/summary?limit=${limit}`); const transformed = transformTodayIssueResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('TodayIssue API Error:', err); } finally { setLoading(false); } }, [limit]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 7-1. PastIssue Hook (이전 이슈 - 날짜별 조회) // ============================================ export function usePastIssue(date: string | null, limit: number = 30) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async () => { if (!date) return; try { setLoading(true); setError(null); const apiData = await fetchApi( `today-issues/summary?limit=${limit}&date=${date}` ); const transformed = transformTodayIssueResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('PastIssue API Error:', err); } finally { setLoading(false); } }, [date, limit]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 8. Calendar Hook // ============================================ export interface CalendarData { items: CalendarScheduleItem[]; totalCount: number; } export interface UseCalendarOptions { /** 조회 시작일 (Y-m-d, 기본: 이번 달 1일) */ startDate?: string; /** 조회 종료일 (Y-m-d, 기본: 이번 달 말일) */ endDate?: string; /** 일정 타입 필터 (schedule|order|construction|other|null=전체) */ type?: 'schedule' | 'order' | 'construction' | 'other' | null; /** 부서 필터 (all|department|personal) */ departmentFilter?: 'all' | 'department' | 'personal'; } export function useCalendar(options: UseCalendarOptions = {}) { const { startDate, endDate, type, departmentFilter = 'all' } = options; const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); // 쿼리 파라미터 구성 const params = new URLSearchParams(); if (startDate) params.append('start_date', startDate); if (endDate) params.append('end_date', endDate); if (type) params.append('type', type); if (departmentFilter) params.append('department_filter', departmentFilter); const queryString = params.toString(); const endpoint = queryString ? `calendar/schedules?${queryString}` : 'calendar/schedules'; const apiData = await fetchApi(endpoint); const transformed = transformCalendarResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('Calendar API Error:', err); } finally { setLoading(false); } }, [startDate, endDate, type, departmentFilter]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 9. Vat Hook // ============================================ export interface UseVatOptions { /** 기간 타입 (quarter: 분기, half: 반기, year: 연간) */ periodType?: 'quarter' | 'half' | 'year'; /** 연도 (기본: 현재 연도) */ year?: number; /** 기간 번호 (quarter: 1-4, half: 1-2) */ period?: number; } export function useVat(options: UseVatOptions = {}) { const { periodType = 'quarter', year, period } = options; const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); // 쿼리 파라미터 구성 const params = new URLSearchParams(); params.append('period_type', periodType); if (year) params.append('year', year.toString()); if (period) params.append('period', period.toString()); const queryString = params.toString(); const endpoint = `vat/summary?${queryString}`; const apiData = await fetchApi(endpoint); const transformed = transformVatResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('Vat API Error:', err); } finally { setLoading(false); } }, [periodType, year, period]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 10. Entertainment Hook (접대비) // ============================================ export interface UseEntertainmentOptions { /** 기간 타입 (annual: 연간, quarterly: 분기) */ limitType?: 'annual' | 'quarterly'; /** 기업 유형 (large: 대기업, medium: 중견기업, small: 중소기업) */ companyType?: 'large' | 'medium' | 'small'; /** 연도 (기본: 현재 연도) */ year?: number; /** 분기 번호 (1-4, 기본: 현재 분기) */ quarter?: number; } export function useEntertainment(options: UseEntertainmentOptions = {}) { const { limitType = 'quarterly', companyType = 'medium', year, quarter } = options; const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); // 쿼리 파라미터 구성 const params = new URLSearchParams(); params.append('limit_type', limitType); params.append('company_type', companyType); if (year) params.append('year', year.toString()); if (quarter) params.append('quarter', quarter.toString()); const queryString = params.toString(); const endpoint = `entertainment/summary?${queryString}`; const apiData = await fetchApi(endpoint); const transformed = transformEntertainmentResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('Entertainment API Error:', err); } finally { setLoading(false); } }, [limitType, companyType, year, quarter]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 11. Welfare Hook (복리후생비) // ============================================ export interface UseWelfareOptions { /** 기간 타입 (annual: 연간, quarterly: 분기) */ limitType?: 'annual' | 'quarterly'; /** 계산 방식 (fixed: 1인당 정액, ratio: 급여 대비 비율) */ calculationType?: 'fixed' | 'ratio'; /** 1인당 월 정액 (calculation_type=fixed일 때 사용, 기본: 200000) */ fixedAmountPerMonth?: number; /** 급여 대비 비율 (calculation_type=ratio일 때 사용, 기본: 0.05) */ ratio?: number; /** 연도 (기본: 현재 연도) */ year?: number; /** 분기 번호 (1-4, 기본: 현재 분기) */ quarter?: number; } export function useWelfare(options: UseWelfareOptions = {}) { const { limitType = 'quarterly', calculationType = 'fixed', fixedAmountPerMonth, ratio, year, quarter, } = options; const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); // 쿼리 파라미터 구성 const params = new URLSearchParams(); params.append('limit_type', limitType); params.append('calculation_type', calculationType); if (fixedAmountPerMonth) params.append('fixed_amount_per_month', fixedAmountPerMonth.toString()); if (ratio) params.append('ratio', ratio.toString()); if (year) params.append('year', year.toString()); if (quarter) params.append('quarter', quarter.toString()); const queryString = params.toString(); const endpoint = `welfare/summary?${queryString}`; const apiData = await fetchApi(endpoint); const transformed = transformWelfareResponse(apiData); setData(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('Welfare API Error:', err); } finally { setLoading(false); } }, [limitType, calculationType, fixedAmountPerMonth, ratio, year, quarter]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // ============================================ // 12. WelfareDetail Hook (복리후생비 상세 - 모달용) // ============================================ export interface UseWelfareDetailOptions { /** 계산 방식 (fixed: 1인당 정액, ratio: 급여 대비 비율) */ calculationType?: 'fixed' | 'ratio'; /** 1인당 월 정액 (calculation_type=fixed일 때 사용, 기본: 200000) */ fixedAmountPerMonth?: number; /** 급여 대비 비율 (calculation_type=ratio일 때 사용, 기본: 0.05) */ ratio?: number; /** 연도 (기본: 현재 연도) */ year?: number; /** 분기 번호 (1-4, 기본: 현재 분기) */ quarter?: number; } /** * 복리후생비 상세 데이터 Hook (모달용) * API에서 상세 데이터를 가져와 DetailModalConfig로 변환 */ export function useWelfareDetail(options: UseWelfareDetailOptions = {}) { const { calculationType = 'fixed', fixedAmountPerMonth, ratio, year, quarter, } = options; const [modalConfig, setModalConfig] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); // 쿼리 파라미터 구성 const params = new URLSearchParams(); params.append('calculation_type', calculationType); if (fixedAmountPerMonth) params.append('fixed_amount_per_month', fixedAmountPerMonth.toString()); if (ratio) params.append('ratio', ratio.toString()); if (year) params.append('year', year.toString()); if (quarter) params.append('quarter', quarter.toString()); const queryString = params.toString(); const endpoint = `welfare/detail?${queryString}`; const apiData = await fetchApi(endpoint); const transformed = transformWelfareDetailResponse(apiData); setModalConfig(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('WelfareDetail API Error:', err); } finally { setLoading(false); } }, [calculationType, fixedAmountPerMonth, ratio, year, quarter]); return { modalConfig, loading, error, refetch: fetchData }; } // ============================================ // 13. PurchaseDetail Hook (매입 상세 - me1 모달용) // ============================================ /** * 매입 상세 데이터 Hook (me1 모달용) * API에서 상세 데이터를 가져와 DetailModalConfig로 변환 */ export function usePurchaseDetail() { const [modalConfig, setModalConfig] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const response = await fetch('/api/v1/purchases/dashboard-detail'); if (!response.ok) { throw new Error(`API 오류: ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || '데이터 조회 실패'); } const transformed = transformPurchaseDetailResponse(result.data); setModalConfig(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('PurchaseDetail API Error:', err); } finally { setLoading(false); } }, []); return { modalConfig, loading, error, refetch: fetchData }; } // ============================================ // 14. CardDetail Hook (카드 상세 - me2 모달용) // ============================================ /** * 카드 상세 데이터 Hook (me2 모달용) * API에서 상세 데이터를 가져와 DetailModalConfig로 변환 */ export function useCardDetail() { const [modalConfig, setModalConfig] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const response = await fetch('/api/v1/card-transactions/dashboard'); if (!response.ok) { throw new Error(`API 오류: ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || '데이터 조회 실패'); } const transformed = transformCardDetailResponse(result.data); setModalConfig(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('CardDetail API Error:', err); } finally { setLoading(false); } }, []); return { modalConfig, loading, error, refetch: fetchData }; } // ============================================ // 15. BillDetail Hook (발행어음 상세 - me3 모달용) // ============================================ /** * 발행어음 상세 데이터 Hook (me3 모달용) * API에서 상세 데이터를 가져와 DetailModalConfig로 변환 */ export function useBillDetail() { const [modalConfig, setModalConfig] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const response = await fetch('/api/v1/bills/dashboard-detail'); if (!response.ok) { throw new Error(`API 오류: ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || '데이터 조회 실패'); } const transformed = transformBillDetailResponse(result.data); setModalConfig(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('BillDetail API Error:', err); } finally { setLoading(false); } }, []); return { modalConfig, loading, error, refetch: fetchData }; } // ============================================ // 16. ExpectedExpenseDetail Hook (지출예상 상세 - me4 모달용) // ============================================ /** * 지출예상 상세 데이터 Hook (me4 모달용) * API에서 상세 데이터를 가져와 DetailModalConfig로 변환 */ export function useExpectedExpenseDetail() { const [modalConfig, setModalConfig] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const response = await fetch('/api/v1/expected-expenses/dashboard-detail'); if (!response.ok) { throw new Error(`API 오류: ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || '데이터 조회 실패'); } const transformed = transformExpectedExpenseDetailResponse(result.data); setModalConfig(transformed); } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('ExpectedExpenseDetail API Error:', err); } finally { setLoading(false); } }, []); return { modalConfig, loading, error, refetch: fetchData }; } // ============================================ // 17. MonthlyExpenseDetail Hook (당월 예상 지출 상세 - 통합 모달용) // ============================================ export type MonthlyExpenseCardId = 'me1' | 'me2' | 'me3' | 'me4'; /** * 당월 예상 지출 상세 데이터 Hook (통합 모달용) * cardId에 따라 다른 API를 호출하고 DetailModalConfig로 변환 * * @example * const { modalConfig, loading, error, fetchData } = useMonthlyExpenseDetail(); * await fetchData('me1'); // 매입 상세 API 호출 */ export function useMonthlyExpenseDetail() { const [modalConfig, setModalConfig] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async (cardId: MonthlyExpenseCardId) => { try { setLoading(true); setError(null); // 모든 카드가 expected-expenses API를 사용하여 데이터 일관성 보장 // transaction_type: me1=purchase, me2=card, me3=bill, me4=전체 let endpoint: string; let transactionType: string | null = null; switch (cardId) { case 'me1': transactionType = 'purchase'; break; case 'me2': transactionType = 'card'; break; case 'me3': transactionType = 'bill'; break; case 'me4': transactionType = null; // 전체 조회 break; default: throw new Error(`Unknown cardId: ${cardId}`); } // 단일 API 엔드포인트 사용 (transaction_type으로 필터링) endpoint = transactionType ? `/api/proxy/expected-expenses/dashboard-detail?transaction_type=${transactionType}` : '/api/proxy/expected-expenses/dashboard-detail'; const transformer = (data: unknown) => transformExpectedExpenseDetailResponse(data as ExpectedExpenseDashboardDetailApiResponse, cardId); const response = await fetch(endpoint); if (!response.ok) { throw new Error(`API 오류: ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || '데이터 조회 실패'); } const transformed = transformer(result.data); setModalConfig(transformed); return transformed; } catch (err) { const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; setError(errorMessage); console.error('MonthlyExpenseDetail API Error:', err); return null; } finally { setLoading(false); } }, []); return { modalConfig, loading, error, fetchData }; } // ============================================ // 통합 Dashboard Hook (선택적 사용) // ============================================ export interface UseCEODashboardOptions { /** DailyReport 섹션 활성화 */ dailyReport?: boolean; /** Receivable 섹션 활성화 */ receivable?: boolean; /** DebtCollection 섹션 활성화 */ debtCollection?: boolean; /** MonthlyExpense 섹션 활성화 */ monthlyExpense?: boolean; /** CardManagement 섹션 활성화 */ cardManagement?: boolean; /** CardManagement fallback 데이터 (가지급금, 법인세, 종합세 등) */ cardManagementFallback?: CardManagementData; /** StatusBoard 섹션 활성화 */ statusBoard?: boolean; } export interface CEODashboardState { dailyReport: { data: DailyReportData | null; loading: boolean; error: string | null; }; receivable: { data: ReceivableData | null; loading: boolean; error: string | null; }; debtCollection: { data: DebtCollectionData | null; loading: boolean; error: string | null; }; monthlyExpense: { data: MonthlyExpenseData | null; loading: boolean; error: string | null; }; cardManagement: { data: CardManagementData | null; loading: boolean; error: string | null; }; statusBoard: { data: TodayIssueItem[] | null; loading: boolean; error: string | null; }; refetchAll: () => void; } /** * 통합 CEO Dashboard Hook * 여러 섹션의 API를 병렬로 호출하여 성능 최적화 */ export function useCEODashboard(options: UseCEODashboardOptions = {}): CEODashboardState { const { dailyReport: enableDailyReport = true, receivable: enableReceivable = true, debtCollection: enableDebtCollection = true, monthlyExpense: enableMonthlyExpense = true, cardManagement: enableCardManagement = true, cardManagementFallback, statusBoard: enableStatusBoard = true, } = options; // 각 섹션별 상태 const [dailyReportData, setDailyReportData] = useState(null); const [dailyReportLoading, setDailyReportLoading] = useState(enableDailyReport); const [dailyReportError, setDailyReportError] = useState(null); const [receivableData, setReceivableData] = useState(null); const [receivableLoading, setReceivableLoading] = useState(enableReceivable); const [receivableError, setReceivableError] = useState(null); const [debtCollectionData, setDebtCollectionData] = useState(null); const [debtCollectionLoading, setDebtCollectionLoading] = useState(enableDebtCollection); const [debtCollectionError, setDebtCollectionError] = useState(null); const [monthlyExpenseData, setMonthlyExpenseData] = useState(null); const [monthlyExpenseLoading, setMonthlyExpenseLoading] = useState(enableMonthlyExpense); const [monthlyExpenseError, setMonthlyExpenseError] = useState(null); const [cardManagementData, setCardManagementData] = useState(null); const [cardManagementLoading, setCardManagementLoading] = useState(enableCardManagement); const [cardManagementError, setCardManagementError] = useState(null); const [statusBoardData, setStatusBoardData] = useState(null); const [statusBoardLoading, setStatusBoardLoading] = useState(enableStatusBoard); const [statusBoardError, setStatusBoardError] = useState(null); // 개별 fetch 함수들 const fetchDailyReport = useCallback(async () => { if (!enableDailyReport) return; try { setDailyReportLoading(true); setDailyReportError(null); const apiData = await fetchApi('daily-report/summary'); setDailyReportData(transformDailyReportResponse(apiData)); } catch (err) { setDailyReportError(err instanceof Error ? err.message : '데이터 로딩 실패'); } finally { setDailyReportLoading(false); } }, [enableDailyReport]); const fetchReceivable = useCallback(async () => { if (!enableReceivable) return; try { setReceivableLoading(true); setReceivableError(null); const apiData = await fetchApi('receivables/summary'); setReceivableData(transformReceivableResponse(apiData)); } catch (err) { setReceivableError(err instanceof Error ? err.message : '데이터 로딩 실패'); } finally { setReceivableLoading(false); } }, [enableReceivable]); const fetchDebtCollection = useCallback(async () => { if (!enableDebtCollection) return; try { setDebtCollectionLoading(true); setDebtCollectionError(null); const apiData = await fetchApi('bad-debts/summary'); setDebtCollectionData(transformDebtCollectionResponse(apiData)); } catch (err) { setDebtCollectionError(err instanceof Error ? err.message : '데이터 로딩 실패'); } finally { setDebtCollectionLoading(false); } }, [enableDebtCollection]); const fetchMonthlyExpense = useCallback(async () => { if (!enableMonthlyExpense) return; try { setMonthlyExpenseLoading(true); setMonthlyExpenseError(null); const apiData = await fetchApi('expected-expenses/summary'); setMonthlyExpenseData(transformMonthlyExpenseResponse(apiData)); } catch (err) { setMonthlyExpenseError(err instanceof Error ? err.message : '데이터 로딩 실패'); } finally { setMonthlyExpenseLoading(false); } }, [enableMonthlyExpense]); const fetchCardManagement = useCallback(async () => { if (!enableCardManagement) return; try { setCardManagementLoading(true); setCardManagementError(null); // 3개 API 병렬 호출: 카드거래, 가지급금, 세금 시뮬레이션 const [cardApiData, loanResponse, taxResponse] = await Promise.all([ fetchApi('card-transactions/summary'), fetchLoanDashboard(), fetchTaxSimulation(), ]); // LoanDashboard와 TaxSimulation은 ApiResponse wrapper가 있으므로 data 추출 const loanData = loanResponse.success ? loanResponse.data : null; const taxData = taxResponse.success ? taxResponse.data : null; setCardManagementData( transformCardManagementResponse(cardApiData, loanData, taxData, cardManagementFallback) ); } catch (err) { setCardManagementError(err instanceof Error ? err.message : '데이터 로딩 실패'); } finally { setCardManagementLoading(false); } }, [enableCardManagement, cardManagementFallback]); const fetchStatusBoard = useCallback(async () => { if (!enableStatusBoard) return; try { setStatusBoardLoading(true); setStatusBoardError(null); const apiData = await fetchApi('status-board/summary'); setStatusBoardData(transformStatusBoardResponse(apiData)); } catch (err) { setStatusBoardError(err instanceof Error ? err.message : '데이터 로딩 실패'); } finally { setStatusBoardLoading(false); } }, [enableStatusBoard]); // 전체 refetch const refetchAll = useCallback(() => { fetchDailyReport(); fetchReceivable(); fetchDebtCollection(); fetchMonthlyExpense(); fetchCardManagement(); fetchStatusBoard(); }, [fetchDailyReport, fetchReceivable, fetchDebtCollection, fetchMonthlyExpense, fetchCardManagement, fetchStatusBoard]); // 초기 로드 useEffect(() => { refetchAll(); }, [refetchAll]); return { dailyReport: { data: dailyReportData, loading: dailyReportLoading, error: dailyReportError, }, receivable: { data: receivableData, loading: receivableLoading, error: receivableError, }, debtCollection: { data: debtCollectionData, loading: debtCollectionLoading, error: debtCollectionError, }, monthlyExpense: { data: monthlyExpenseData, loading: monthlyExpenseLoading, error: monthlyExpenseError, }, cardManagement: { data: cardManagementData, loading: cardManagementLoading, error: cardManagementError, }, statusBoard: { data: statusBoardData, loading: statusBoardLoading, error: statusBoardError, }, refetchAll, }; }