422 lines
13 KiB
TypeScript
422 lines
13 KiB
TypeScript
|
|
'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,
|
||
|
|
};
|
||
|
|
}
|