'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, } from '@/lib/api/dashboard/types'; import { transformDailyReportResponse, transformReceivableResponse, transformDebtCollectionResponse, transformMonthlyExpenseResponse, transformCardManagementResponse, transformStatusBoardResponse, transformTodayIssueResponse, transformCalendarResponse, transformVatResponse, transformEntertainmentResponse, transformWelfareResponse, transformWelfareDetailResponse, } 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); const apiData = await fetchApi('card-transactions/summary'); const transformed = transformCardManagementResponse(apiData, 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 }; } // ============================================ // 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 }; } // ============================================ // 통합 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); const apiData = await fetchApi('card-transactions/summary'); setCardManagementData(transformCardManagementResponse(apiData, 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, }; }