Files
sam-react-prod/src/hooks/useCEODashboard.ts

852 lines
27 KiB
TypeScript
Raw Normal View History

'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<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 };
}
// ============================================
// 6. StatusBoard Hook
// ============================================
export function useStatusBoard() {
const [data, setData] = useState<TodayIssueItem[] | 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<StatusBoardApiResponse>('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<TodayIssueData | 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<TodayIssueApiResponse>(`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<CalendarData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<CalendarApiResponse>(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<VatData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<VatApiResponse>(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<EntertainmentData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<EntertainmentApiResponse>(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<WelfareData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<WelfareApiResponse>(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<DetailModalConfig | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(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<WelfareDetailApiResponse>(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<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);
const [statusBoardData, setStatusBoardData] = useState<TodayIssueItem[] | null>(null);
const [statusBoardLoading, setStatusBoardLoading] = useState(enableStatusBoard);
const [statusBoardError, setStatusBoardError] = 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]);
const fetchStatusBoard = useCallback(async () => {
if (!enableStatusBoard) return;
try {
setStatusBoardLoading(true);
setStatusBoardError(null);
const apiData = await fetchApi<StatusBoardApiResponse>('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,
};
}