feat: CEO 대시보드 Phase 1 API 연동 (5개 섹션)
- DailyReport, Receivable, DebtCollection, MonthlyExpense, CardManagement API 연동 - useCEODashboard Hook 추가 (병렬 API 호출) - API → Frontend 타입 변환 함수 및 CheckPoint 생성 로직 구현 - API 실패 시 mockData fallback 패턴 적용
This commit is contained in:
422
src/hooks/useCEODashboard.ts
Normal file
422
src/hooks/useCEODashboard.ts
Normal file
@@ -0,0 +1,422 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* CEO Dashboard API 연동 Hook
|
||||
*
|
||||
* 각 섹션별 API 호출 및 데이터 변환 담당
|
||||
* 참조 패턴: useClientList.ts
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
|
||||
import type {
|
||||
DailyReportApiResponse,
|
||||
ReceivablesApiResponse,
|
||||
BadDebtApiResponse,
|
||||
ExpectedExpenseApiResponse,
|
||||
CardTransactionApiResponse,
|
||||
} from '@/lib/api/dashboard/types';
|
||||
|
||||
import {
|
||||
transformDailyReportResponse,
|
||||
transformReceivableResponse,
|
||||
transformDebtCollectionResponse,
|
||||
transformMonthlyExpenseResponse,
|
||||
transformCardManagementResponse,
|
||||
} from '@/lib/api/dashboard/transformers';
|
||||
|
||||
import type {
|
||||
DailyReportData,
|
||||
ReceivableData,
|
||||
DebtCollectionData,
|
||||
MonthlyExpenseData,
|
||||
CardManagementData,
|
||||
} from '@/components/business/CEODashboard/types';
|
||||
|
||||
// ============================================
|
||||
// 공통 fetch 유틸리티
|
||||
// ============================================
|
||||
|
||||
async function fetchApi<T>(endpoint: string): Promise<T> {
|
||||
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<DailyReportData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const apiData = await fetchApi<DailyReportApiResponse>('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<ReceivableData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const apiData = await fetchApi<ReceivablesApiResponse>('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<DebtCollectionData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const apiData = await fetchApi<BadDebtApiResponse>('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<MonthlyExpenseData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const apiData = await fetchApi<ExpectedExpenseApiResponse>('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<CardManagementData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const apiData = await fetchApi<CardTransactionApiResponse>('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 };
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 통합 Dashboard Hook (선택적 사용)
|
||||
// ============================================
|
||||
|
||||
export interface UseCEODashboardOptions {
|
||||
/** DailyReport 섹션 활성화 */
|
||||
dailyReport?: boolean;
|
||||
/** Receivable 섹션 활성화 */
|
||||
receivable?: boolean;
|
||||
/** DebtCollection 섹션 활성화 */
|
||||
debtCollection?: boolean;
|
||||
/** MonthlyExpense 섹션 활성화 */
|
||||
monthlyExpense?: boolean;
|
||||
/** CardManagement 섹션 활성화 */
|
||||
cardManagement?: boolean;
|
||||
/** CardManagement fallback 데이터 */
|
||||
cardManagementFallback?: CardManagementData;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
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,
|
||||
} = options;
|
||||
|
||||
// 각 섹션별 상태
|
||||
const [dailyReportData, setDailyReportData] = useState<DailyReportData | null>(null);
|
||||
const [dailyReportLoading, setDailyReportLoading] = useState(enableDailyReport);
|
||||
const [dailyReportError, setDailyReportError] = useState<string | null>(null);
|
||||
|
||||
const [receivableData, setReceivableData] = useState<ReceivableData | null>(null);
|
||||
const [receivableLoading, setReceivableLoading] = useState(enableReceivable);
|
||||
const [receivableError, setReceivableError] = useState<string | null>(null);
|
||||
|
||||
const [debtCollectionData, setDebtCollectionData] = useState<DebtCollectionData | null>(null);
|
||||
const [debtCollectionLoading, setDebtCollectionLoading] = useState(enableDebtCollection);
|
||||
const [debtCollectionError, setDebtCollectionError] = useState<string | null>(null);
|
||||
|
||||
const [monthlyExpenseData, setMonthlyExpenseData] = useState<MonthlyExpenseData | null>(null);
|
||||
const [monthlyExpenseLoading, setMonthlyExpenseLoading] = useState(enableMonthlyExpense);
|
||||
const [monthlyExpenseError, setMonthlyExpenseError] = useState<string | null>(null);
|
||||
|
||||
const [cardManagementData, setCardManagementData] = useState<CardManagementData | null>(null);
|
||||
const [cardManagementLoading, setCardManagementLoading] = useState(enableCardManagement);
|
||||
const [cardManagementError, setCardManagementError] = useState<string | null>(null);
|
||||
|
||||
// 개별 fetch 함수들
|
||||
const fetchDailyReport = useCallback(async () => {
|
||||
if (!enableDailyReport) return;
|
||||
try {
|
||||
setDailyReportLoading(true);
|
||||
setDailyReportError(null);
|
||||
const apiData = await fetchApi<DailyReportApiResponse>('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<ReceivablesApiResponse>('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<BadDebtApiResponse>('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<ExpectedExpenseApiResponse>('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<CardTransactionApiResponse>('card-transactions/summary');
|
||||
setCardManagementData(transformCardManagementResponse(apiData, cardManagementFallback));
|
||||
} catch (err) {
|
||||
setCardManagementError(err instanceof Error ? err.message : '데이터 로딩 실패');
|
||||
} finally {
|
||||
setCardManagementLoading(false);
|
||||
}
|
||||
}, [enableCardManagement, cardManagementFallback]);
|
||||
|
||||
// 전체 refetch
|
||||
const refetchAll = useCallback(() => {
|
||||
fetchDailyReport();
|
||||
fetchReceivable();
|
||||
fetchDebtCollection();
|
||||
fetchMonthlyExpense();
|
||||
fetchCardManagement();
|
||||
}, [fetchDailyReport, fetchReceivable, fetchDebtCollection, fetchMonthlyExpense, fetchCardManagement]);
|
||||
|
||||
// 초기 로드
|
||||
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,
|
||||
},
|
||||
refetchAll,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user