+
+
+ 견적 {itemResult.index + 1}
+
+
+ {itemResult.result.finished_goods?.name || product?.item_name || formItem?.productName || "-"}
+
+
+ ({itemResult.result.finished_goods?.code || formItem?.productName || "-"})
+
+
+
+
+ 단가: {(itemResult.result.grand_total || 0).toLocaleString()}원
-
-
- 단가: {itemResult.result.grand_total.toLocaleString()}원
-
-
- 합계: {((itemResult.result.grand_total || 0) * (formItem?.quantity || 1)).toLocaleString()}원
-
- (×{formItem?.quantity || 1})
-
-
+
+ 합계: {((itemResult.result.grand_total || 0) * (formItem?.quantity || 1)).toLocaleString()}원
+
+ (×{formItem?.quantity || 1})
+
+
{/* BOM 상세 내역 */}
{itemResult.result.items && itemResult.result.items.length > 0 && (
diff --git a/src/hooks/useCEODashboard.ts b/src/hooks/useCEODashboard.ts
new file mode 100644
index 00000000..55377f16
--- /dev/null
+++ b/src/hooks/useCEODashboard.ts
@@ -0,0 +1,784 @@
+'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,
+} from '@/lib/api/dashboard/types';
+
+import {
+ transformDailyReportResponse,
+ transformReceivableResponse,
+ transformDebtCollectionResponse,
+ transformMonthlyExpenseResponse,
+ transformCardManagementResponse,
+ transformStatusBoardResponse,
+ transformTodayIssueResponse,
+ transformCalendarResponse,
+ transformVatResponse,
+ transformEntertainmentResponse,
+ transformWelfareResponse,
+} from '@/lib/api/dashboard/transformers';
+
+import type {
+ DailyReportData,
+ ReceivableData,
+ DebtCollectionData,
+ MonthlyExpenseData,
+ CardManagementData,
+ TodayIssueItem,
+ TodayIssueListItem,
+ CalendarScheduleItem,
+ VatData,
+ EntertainmentData,
+ WelfareData,
+} 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 };
+}
+
+// ============================================
+// 통합 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,
+ };
+}
\ No newline at end of file
diff --git a/src/lib/api/dashboard/index.ts b/src/lib/api/dashboard/index.ts
new file mode 100644
index 00000000..c831c140
--- /dev/null
+++ b/src/lib/api/dashboard/index.ts
@@ -0,0 +1,6 @@
+/**
+ * CEO Dashboard API 모듈 export
+ */
+
+export * from './types';
+export * from './transformers';
\ No newline at end of file
diff --git a/src/lib/api/dashboard/transformers.ts b/src/lib/api/dashboard/transformers.ts
new file mode 100644
index 00000000..f8bc53db
--- /dev/null
+++ b/src/lib/api/dashboard/transformers.ts
@@ -0,0 +1,642 @@
+/**
+ * CEO Dashboard API 응답 → Frontend 타입 변환 함수
+ *
+ * 참조: docs/plans/AI_리포트_키워드_색상체계_가이드_v1.4.md
+ */
+
+import type {
+ DailyReportApiResponse,
+ ReceivablesApiResponse,
+ BadDebtApiResponse,
+ ExpectedExpenseApiResponse,
+ CardTransactionApiResponse,
+ StatusBoardApiResponse,
+ TodayIssueApiResponse,
+ CalendarApiResponse,
+ VatApiResponse,
+ EntertainmentApiResponse,
+ WelfareApiResponse,
+} from './types';
+
+import type {
+ DailyReportData,
+ ReceivableData,
+ DebtCollectionData,
+ MonthlyExpenseData,
+ CardManagementData,
+ TodayIssueItem,
+ TodayIssueListItem,
+ TodayIssueListBadgeType,
+ CalendarScheduleItem,
+ CheckPoint,
+ CheckPointType,
+ VatData,
+ EntertainmentData,
+ WelfareData,
+ HighlightColor,
+} from '@/components/business/CEODashboard/types';
+
+// ============================================
+// 헬퍼 함수
+// ============================================
+
+/**
+ * 금액 포맷팅
+ * @example formatAmount(3050000000) → "30.5억원"
+ */
+function formatAmount(amount: number): string {
+ const absAmount = Math.abs(amount);
+ if (absAmount >= 100000000) {
+ return `${(amount / 100000000).toFixed(1)}억원`;
+ } else if (absAmount >= 10000) {
+ return `${Math.round(amount / 10000).toLocaleString()}만원`;
+ }
+ return `${amount.toLocaleString()}원`;
+}
+
+/**
+ * 날짜 포맷팅 (API → 한국어 형식)
+ * @example formatDate("2026-01-20", "월요일") → "2026년 1월 20일 월요일"
+ */
+function formatDate(dateStr: string, dayOfWeek: string): string {
+ const date = new Date(dateStr);
+ return `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일 ${dayOfWeek}`;
+}
+
+/**
+ * 퍼센트 변화율 계산
+ */
+function calculateChangeRate(current: number, previous: number): number {
+ if (previous === 0) return current > 0 ? 100 : 0;
+ return ((current - previous) / previous) * 100;
+}
+
+// ============================================
+// 1. DailyReport 변환
+// ============================================
+
+/**
+ * 일일 일보 CheckPoints 생성
+ * 참조: AI 리포트 색상 체계 가이드 - 섹션 2
+ */
+function generateDailyReportCheckPoints(api: DailyReportApiResponse): CheckPoint[] {
+ const checkPoints: CheckPoint[] = [];
+
+ // 출금 정보
+ const withdrawal = api.krw_totals.expense;
+ if (withdrawal > 0) {
+ checkPoints.push({
+ id: 'dr-withdrawal',
+ type: 'info' as CheckPointType,
+ message: `어제 ${formatAmount(withdrawal)} 출금했습니다.`,
+ highlights: [
+ { text: formatAmount(withdrawal), color: 'red' as const },
+ ],
+ });
+ }
+
+ // 입금 정보
+ const deposit = api.krw_totals.income;
+ if (deposit > 0) {
+ checkPoints.push({
+ id: 'dr-deposit',
+ type: 'success' as CheckPointType,
+ message: `어제 ${formatAmount(deposit)}이 입금되었습니다.`,
+ highlights: [
+ { text: formatAmount(deposit), color: 'green' as const },
+ { text: '입금', color: 'green' as const },
+ ],
+ });
+ }
+
+ // 현금성 자산 현황
+ const cashAsset = api.cash_asset_total;
+ checkPoints.push({
+ id: 'dr-cash-asset',
+ type: 'info' as CheckPointType,
+ message: `총 현금성 자산이 ${formatAmount(cashAsset)}입니다.`,
+ highlights: [
+ { text: formatAmount(cashAsset), color: 'blue' as const },
+ ],
+ });
+
+ return checkPoints;
+}
+
+/**
+ * DailyReport API 응답 → Frontend 타입 변환
+ */
+export function transformDailyReportResponse(api: DailyReportApiResponse): DailyReportData {
+ return {
+ date: formatDate(api.date, api.day_of_week),
+ cards: [
+ {
+ id: 'dr1',
+ label: '현금성 자산 합계',
+ amount: api.cash_asset_total,
+ },
+ {
+ id: 'dr2',
+ label: '외국환(USD) 합계',
+ amount: api.foreign_currency_total,
+ currency: 'USD',
+ },
+ {
+ id: 'dr3',
+ label: '입금 합계',
+ amount: api.krw_totals.income,
+ },
+ {
+ id: 'dr4',
+ label: '출금 합계',
+ amount: api.krw_totals.expense,
+ },
+ ],
+ checkPoints: generateDailyReportCheckPoints(api),
+ };
+}
+
+// ============================================
+// 2. Receivable 변환
+// ============================================
+
+/**
+ * 미수금 현황 CheckPoints 생성
+ */
+function generateReceivableCheckPoints(api: ReceivablesApiResponse): CheckPoint[] {
+ const checkPoints: CheckPoint[] = [];
+
+ // 연체 거래처 경고
+ if (api.overdue_vendor_count > 0) {
+ checkPoints.push({
+ id: 'rv-overdue',
+ type: 'warning' as CheckPointType,
+ message: `연체 거래처 ${api.overdue_vendor_count}곳. 회수 조치가 필요합니다.`,
+ highlights: [
+ { text: `연체 거래처 ${api.overdue_vendor_count}곳`, color: 'red' as const },
+ ],
+ });
+ }
+
+ // 미수금 현황
+ if (api.total_receivables > 0) {
+ checkPoints.push({
+ id: 'rv-total',
+ type: 'info' as CheckPointType,
+ message: `총 미수금 ${formatAmount(api.total_receivables)}입니다.`,
+ highlights: [
+ { text: formatAmount(api.total_receivables), color: 'blue' as const },
+ ],
+ });
+ }
+
+ return checkPoints;
+}
+
+/**
+ * Receivables API 응답 → Frontend 타입 변환
+ */
+export function transformReceivableResponse(api: ReceivablesApiResponse): ReceivableData {
+ // 누적 미수금 = 이월 + 매출 - 입금
+ const cumulativeReceivable = api.total_carry_forward + api.total_sales - api.total_deposits;
+
+ return {
+ cards: [
+ {
+ id: 'rv1',
+ label: '누적 미수금',
+ amount: cumulativeReceivable,
+ subItems: [
+ { label: '이월', value: api.total_carry_forward },
+ { label: '매출', value: api.total_sales },
+ { label: '입금', value: api.total_deposits },
+ ],
+ },
+ {
+ id: 'rv2',
+ label: '당월 미수금',
+ amount: api.total_receivables,
+ subItems: [
+ { label: '매출', value: api.total_sales },
+ { label: '입금', value: api.total_deposits },
+ ],
+ },
+ {
+ id: 'rv3',
+ label: '거래처 현황',
+ amount: api.vendor_count,
+ unit: '곳',
+ subLabel: `연체 ${api.overdue_vendor_count}곳`,
+ },
+ ],
+ checkPoints: generateReceivableCheckPoints(api),
+ detailButtonLabel: '미수금 상세',
+ detailButtonPath: '/accounting/receivables-status',
+ };
+}
+
+// ============================================
+// 3. DebtCollection 변환
+// ============================================
+
+/**
+ * 채권추심 CheckPoints 생성
+ */
+function generateDebtCollectionCheckPoints(api: BadDebtApiResponse): CheckPoint[] {
+ const checkPoints: CheckPoint[] = [];
+
+ // 법적조치 진행 중
+ if (api.legal_action_amount > 0) {
+ checkPoints.push({
+ id: 'dc-legal',
+ type: 'warning' as CheckPointType,
+ message: `법적조치 진행 중 ${formatAmount(api.legal_action_amount)}입니다.`,
+ highlights: [
+ { text: formatAmount(api.legal_action_amount), color: 'red' as const },
+ ],
+ });
+ }
+
+ // 회수 완료
+ if (api.recovered_amount > 0) {
+ checkPoints.push({
+ id: 'dc-recovered',
+ type: 'success' as CheckPointType,
+ message: `총 ${formatAmount(api.recovered_amount)}을 회수 완료했습니다.`,
+ highlights: [
+ { text: formatAmount(api.recovered_amount), color: 'green' as const },
+ { text: '회수 완료', color: 'green' as const },
+ ],
+ });
+ }
+
+ return checkPoints;
+}
+
+/**
+ * BadDebt API 응답 → Frontend 타입 변환
+ */
+export function transformDebtCollectionResponse(api: BadDebtApiResponse): DebtCollectionData {
+ return {
+ cards: [
+ {
+ id: 'dc1',
+ label: '누적 악성채권',
+ amount: api.total_amount,
+ },
+ {
+ id: 'dc2',
+ label: '추심중',
+ amount: api.collecting_amount,
+ },
+ {
+ id: 'dc3',
+ label: '법적조치',
+ amount: api.legal_action_amount,
+ },
+ {
+ id: 'dc4',
+ label: '회수완료',
+ amount: api.recovered_amount,
+ },
+ ],
+ checkPoints: generateDebtCollectionCheckPoints(api),
+ detailButtonPath: '/accounting/bad-debt-collection',
+ };
+}
+
+// ============================================
+// 4. MonthlyExpense 변환
+// ============================================
+
+/**
+ * 당월 예상 지출 CheckPoints 생성
+ */
+function generateMonthlyExpenseCheckPoints(api: ExpectedExpenseApiResponse): CheckPoint[] {
+ const checkPoints: CheckPoint[] = [];
+
+ // 총 예상 지출
+ checkPoints.push({
+ id: 'me-total',
+ type: 'info' as CheckPointType,
+ message: `이번 달 예상 지출은 ${formatAmount(api.total_amount)}입니다.`,
+ highlights: [
+ { text: formatAmount(api.total_amount), color: 'blue' as const },
+ ],
+ });
+
+ return checkPoints;
+}
+
+/**
+ * ExpectedExpense API 응답 → Frontend 타입 변환
+ * 주의: 실제 API는 상세 분류(매입/카드/어음 등)를 제공하지 않음
+ * by_transaction_type에서 추출하거나 기본값 사용
+ */
+export function transformMonthlyExpenseResponse(api: ExpectedExpenseApiResponse): MonthlyExpenseData {
+ // transaction_type별 금액 추출
+ const purchaseTotal = api.by_transaction_type['purchase']?.total ?? 0;
+ const cardTotal = api.by_transaction_type['card']?.total ?? 0;
+ const billTotal = api.by_transaction_type['bill']?.total ?? 0;
+
+ return {
+ cards: [
+ {
+ id: 'me1',
+ label: '매입',
+ amount: purchaseTotal,
+ },
+ {
+ id: 'me2',
+ label: '카드',
+ amount: cardTotal,
+ },
+ {
+ id: 'me3',
+ label: '발행어음',
+ amount: billTotal,
+ },
+ {
+ id: 'me4',
+ label: '총 예상 지출 합계',
+ amount: api.total_amount,
+ },
+ ],
+ checkPoints: generateMonthlyExpenseCheckPoints(api),
+ };
+}
+
+// ============================================
+// 5. CardManagement 변환
+// ============================================
+
+/**
+ * 카드/가지급금 CheckPoints 생성
+ */
+function generateCardManagementCheckPoints(api: CardTransactionApiResponse): CheckPoint[] {
+ const checkPoints: CheckPoint[] = [];
+
+ // 전월 대비 변화
+ const changeRate = calculateChangeRate(api.current_month_total, api.previous_month_total);
+ if (Math.abs(changeRate) > 10) {
+ const type: CheckPointType = changeRate > 0 ? 'warning' : 'info';
+ checkPoints.push({
+ id: 'cm-change',
+ type,
+ message: `당월 카드 사용액이 전월 대비 ${changeRate > 0 ? '+' : ''}${changeRate.toFixed(1)}% 변동했습니다.`,
+ highlights: [
+ { text: `${changeRate > 0 ? '+' : ''}${changeRate.toFixed(1)}%`, color: changeRate > 0 ? 'red' as const : 'green' as const },
+ ],
+ });
+ }
+
+ // 당월 사용액
+ checkPoints.push({
+ id: 'cm-current',
+ type: 'info' as CheckPointType,
+ message: `당월 카드 사용 총 ${formatAmount(api.current_month_total)}입니다.`,
+ highlights: [
+ { text: formatAmount(api.current_month_total), color: 'blue' as const },
+ ],
+ });
+
+ return checkPoints;
+}
+
+/**
+ * CardTransaction API 응답 → Frontend 타입 변환
+ * 주의: 가지급금, 법인세 예상 가중 등은 별도 API 필요 (현재 목업 유지)
+ */
+export function transformCardManagementResponse(
+ api: CardTransactionApiResponse,
+ fallbackData?: CardManagementData
+): CardManagementData {
+ const changeRate = calculateChangeRate(api.current_month_total, api.previous_month_total);
+
+ return {
+ // 가지급금 관련 경고는 API 데이터가 없으므로 fallback 사용
+ warningBanner: fallbackData?.warningBanner,
+ cards: [
+ {
+ id: 'cm1',
+ label: '카드',
+ amount: api.current_month_total,
+ previousLabel: `전월 대비 ${changeRate > 0 ? '+' : ''}${changeRate.toFixed(1)}%`,
+ },
+ // 아래 항목들은 API에서 제공하지 않으므로 fallback 사용
+ fallbackData?.cards[1] ?? {
+ id: 'cm2',
+ label: '가지급금',
+ amount: 0,
+ },
+ fallbackData?.cards[2] ?? {
+ id: 'cm3',
+ label: '법인세 예상 가중',
+ amount: 0,
+ },
+ fallbackData?.cards[3] ?? {
+ id: 'cm4',
+ label: '대표자 종합세 예상 가중',
+ amount: 0,
+ },
+ ],
+ checkPoints: generateCardManagementCheckPoints(api),
+ };
+}
+
+// ============================================
+// 6. StatusBoard 변환
+// ============================================
+
+/**
+ * StatusBoard API 응답 → Frontend 타입 변환
+ * API 응답 형식이 TodayIssueItem과 거의 동일하므로 단순 매핑
+ */
+export function transformStatusBoardResponse(api: StatusBoardApiResponse): TodayIssueItem[] {
+ return api.items.map((item) => ({
+ id: item.id,
+ label: item.label,
+ count: item.count,
+ path: item.path,
+ isHighlighted: item.isHighlighted,
+ }));
+}
+
+// ============================================
+// 7. TodayIssue 변환
+// ============================================
+
+/** 유효한 뱃지 타입 목록 */
+const VALID_BADGE_TYPES: TodayIssueListBadgeType[] = [
+ '수주 성공',
+ '주식 이슈',
+ '직정 제고',
+ '지출예상내역서',
+ '세금 신고',
+ '결재 요청',
+ '기타',
+];
+
+/**
+ * API 뱃지 문자열 → Frontend 뱃지 타입 변환
+ * 유효하지 않은 뱃지는 '기타'로 폴백
+ */
+function validateBadgeType(badge: string): TodayIssueListBadgeType {
+ if (VALID_BADGE_TYPES.includes(badge as TodayIssueListBadgeType)) {
+ return badge as TodayIssueListBadgeType;
+ }
+ return '기타';
+}
+
+/**
+ * TodayIssue API 응답 → Frontend 타입 변환
+ * 오늘의 이슈 리스트 데이터 변환
+ */
+export function transformTodayIssueResponse(api: TodayIssueApiResponse): {
+ items: TodayIssueListItem[];
+ totalCount: number;
+} {
+ return {
+ items: api.items.map((item) => ({
+ id: item.id,
+ badge: validateBadgeType(item.badge),
+ content: item.content,
+ time: item.time,
+ date: item.date,
+ needsApproval: item.needsApproval ?? false,
+ path: item.path,
+ })),
+ totalCount: api.total_count,
+ };
+}
+
+// ============================================
+// 8. Calendar 변환
+// ============================================
+
+/**
+ * Calendar API 응답 → Frontend 타입 변환
+ * API 응답 형식이 CalendarScheduleItem과 동일하므로 단순 매핑
+ */
+export function transformCalendarResponse(api: CalendarApiResponse): {
+ items: CalendarScheduleItem[];
+ totalCount: number;
+} {
+ return {
+ items: api.items.map((item) => ({
+ id: item.id,
+ title: item.title,
+ startDate: item.startDate,
+ endDate: item.endDate,
+ startTime: item.startTime,
+ endTime: item.endTime,
+ isAllDay: item.isAllDay,
+ type: item.type,
+ department: item.department,
+ personName: item.personName,
+ color: item.color,
+ })),
+ totalCount: api.total_count,
+ };
+}
+
+// ============================================
+// 9. Vat 변환
+// ============================================
+
+/** 유효한 하이라이트 색상 목록 */
+const VALID_HIGHLIGHT_COLORS: HighlightColor[] = ['red', 'green', 'blue'];
+
+/**
+ * API 색상 문자열 → Frontend 하이라이트 색상 변환
+ * 유효하지 않은 색상은 'blue'로 폴백
+ */
+function validateHighlightColor(color: string): HighlightColor {
+ if (VALID_HIGHLIGHT_COLORS.includes(color as HighlightColor)) {
+ return color as HighlightColor;
+ }
+ return 'blue';
+}
+
+/**
+ * Vat API 응답 → Frontend 타입 변환
+ * 부가세 현황 데이터 변환
+ */
+export function transformVatResponse(api: VatApiResponse): VatData {
+ return {
+ cards: api.cards.map((card) => ({
+ id: card.id,
+ label: card.label,
+ amount: card.amount,
+ subLabel: card.subLabel,
+ unit: card.unit,
+ })),
+ checkPoints: api.check_points.map((cp) => ({
+ id: cp.id,
+ type: cp.type as CheckPointType,
+ message: cp.message,
+ highlights: cp.highlights?.map((h) => ({
+ text: h.text,
+ color: validateHighlightColor(h.color),
+ })),
+ })),
+ };
+}
+
+// ============================================
+// 10. Entertainment 변환
+// ============================================
+
+/**
+ * Entertainment API 응답 → Frontend 타입 변환
+ * 접대비 현황 데이터 변환
+ */
+export function transformEntertainmentResponse(api: EntertainmentApiResponse): EntertainmentData {
+ return {
+ cards: api.cards.map((card) => ({
+ id: card.id,
+ label: card.label,
+ amount: card.amount,
+ subLabel: card.subLabel,
+ unit: card.unit,
+ })),
+ checkPoints: api.check_points.map((cp) => ({
+ id: cp.id,
+ type: cp.type as CheckPointType,
+ message: cp.message,
+ highlights: cp.highlights?.map((h) => ({
+ text: h.text,
+ color: validateHighlightColor(h.color),
+ })),
+ })),
+ };
+}
+
+// ============================================
+// 11. Welfare 변환
+// ============================================
+
+/**
+ * Welfare API 응답 → Frontend 타입 변환
+ * 복리후생비 현황 데이터 변환
+ */
+export function transformWelfareResponse(api: WelfareApiResponse): WelfareData {
+ return {
+ cards: api.cards.map((card) => ({
+ id: card.id,
+ label: card.label,
+ amount: card.amount,
+ subLabel: card.subLabel,
+ unit: card.unit,
+ })),
+ checkPoints: api.check_points.map((cp) => ({
+ id: cp.id,
+ type: cp.type as CheckPointType,
+ message: cp.message,
+ highlights: cp.highlights?.map((h) => ({
+ text: h.text,
+ color: validateHighlightColor(h.color),
+ })),
+ })),
+ };
+}
\ No newline at end of file
diff --git a/src/lib/api/dashboard/types.ts b/src/lib/api/dashboard/types.ts
new file mode 100644
index 00000000..2cddb9c1
--- /dev/null
+++ b/src/lib/api/dashboard/types.ts
@@ -0,0 +1,286 @@
+/**
+ * CEO Dashboard API 응답 타입 정의
+ *
+ * Laravel API 응답과 Frontend 타입 간의 매핑을 위한 타입들
+ */
+
+// ============================================
+// 1. DailyReport API 응답 타입
+// ============================================
+
+/** KRW/USD 통화별 합계 */
+export interface CurrencyTotals {
+ carryover: number; // 전월 이월
+ income: number; // 수입 (입금)
+ expense: number; // 지출 (출금)
+ balance: number; // 잔액
+}
+
+/** GET /api/proxy/daily-report/summary 응답 */
+export interface DailyReportApiResponse {
+ date: string; // "2026-01-20"
+ day_of_week: string; // "월요일"
+ note_receivable_total: number; // 수취채권 합계
+ foreign_currency_total: number; // 외화 합계 (USD)
+ cash_asset_total: number; // 현금성 자산 합계
+ krw_totals: CurrencyTotals; // 원화 합계
+ usd_totals: CurrencyTotals; // 달러 합계
+}
+
+// ============================================
+// 2. Receivables API 응답 타입
+// ============================================
+
+/** GET /api/proxy/receivables/summary 응답 */
+export interface ReceivablesApiResponse {
+ total_carry_forward: number; // 이월 미수금
+ total_sales: number; // 당월 매출
+ total_deposits: number; // 당월 입금
+ total_bills: number; // 당월 어음
+ total_receivables: number; // 미수금 잔액
+ vendor_count: number; // 거래처 수
+ overdue_vendor_count: number; // 연체 거래처 수
+}
+
+// ============================================
+// 3. BadDebt (채권추심) API 응답 타입
+// ============================================
+
+/** GET /api/proxy/bad-debts/summary 응답 */
+export interface BadDebtApiResponse {
+ total_amount: number; // 총 악성채권
+ collecting_amount: number; // 추심중
+ legal_action_amount: number; // 법적조치
+ recovered_amount: number; // 회수완료
+ bad_debt_amount: number; // 대손처리
+}
+
+// ============================================
+// 4. ExpectedExpense (당월 예상 지출) API 응답 타입
+// ============================================
+
+/** 상태/유형별 집계 아이템 */
+export interface ExpenseSummaryItem {
+ total: number;
+ count: number;
+}
+
+/** GET /api/proxy/expected-expenses/summary 응답 */
+export interface ExpectedExpenseApiResponse {
+ total_amount: number;
+ total_count: number;
+ by_payment_status: Record;
+ by_transaction_type: Record;
+ by_month: Record;
+}
+
+// ============================================
+// 5. CardTransaction (카드/가지급금) API 응답 타입
+// ============================================
+
+/** GET /api/proxy/card-transactions/summary 응답 */
+export interface CardTransactionApiResponse {
+ previous_month_total: number; // 전월 카드 사용액
+ current_month_total: number; // 당월 카드 사용액
+ total_count: number; // 총 건수
+ total_amount: number; // 총 금액
+}
+
+// ============================================
+// 공통 API 응답 Wrapper
+// ============================================
+
+/** 표준 API 응답 래퍼 */
+export interface ApiResponse {
+ success: boolean;
+ message: string;
+ data: T;
+}
+
+// ============================================
+// Dashboard Hook 상태 타입
+// ============================================
+
+/** 섹션별 로딩 상태 */
+export interface DashboardLoadingState {
+ dailyReport: boolean;
+ receivable: boolean;
+ debtCollection: boolean;
+ monthlyExpense: boolean;
+ cardManagement: boolean;
+}
+
+/** 섹션별 에러 상태 */
+export interface DashboardErrorState {
+ dailyReport: string | null;
+ receivable: string | null;
+ debtCollection: string | null;
+ monthlyExpense: string | null;
+ cardManagement: string | null;
+}
+
+// ============================================
+// 6. StatusBoard (현황판) API 응답 타입
+// ============================================
+
+/** 현황판 카드 아이템 */
+export interface StatusBoardItemApiResponse {
+ id: string; // 카드 ID (orders, bad_debts, etc.)
+ label: string; // 카드 라벨
+ count: number | string; // 건수 또는 텍스트 (예: "부가세 신고 D-15")
+ path: string; // 이동 경로
+ isHighlighted: boolean; // 강조 표시 여부
+}
+
+/** GET /api/proxy/status-board/summary 응답 */
+export interface StatusBoardApiResponse {
+ items: StatusBoardItemApiResponse[];
+}
+
+// ============================================
+// 7. TodayIssue (오늘의 이슈 리스트) API 응답 타입
+// ============================================
+
+/** 오늘의 이슈 아이템 */
+export interface TodayIssueItemApiResponse {
+ id: string; // 항목 고유 ID
+ badge: string; // 이슈 카테고리 뱃지
+ content: string; // 이슈 내용
+ time: string; // 상대 시간 (예: "10분 전")
+ date?: string; // 날짜 (ISO 형식)
+ needsApproval?: boolean; // 승인/반려 버튼 표시 여부
+ path?: string; // 클릭 시 이동할 경로
+}
+
+/** GET /api/proxy/today-issues/summary 응답 */
+export interface TodayIssueApiResponse {
+ items: TodayIssueItemApiResponse[];
+ total_count: number; // 전체 이슈 건수
+}
+
+// ============================================
+// 8. Calendar (캘린더) API 응답 타입
+// ============================================
+
+/** 캘린더 일정 타입 */
+export type CalendarScheduleType = 'schedule' | 'order' | 'construction' | 'other';
+
+/** 캘린더 일정 아이템 */
+export interface CalendarScheduleItemApiResponse {
+ id: string; // 일정 ID (타입_ID 형식, 예: "wo_123")
+ title: string; // 일정 제목
+ startDate: string; // 시작일 (Y-m-d)
+ endDate: string; // 종료일 (Y-m-d)
+ startTime: string | null; // 시작 시간 (HH:mm) 또는 null
+ endTime: string | null; // 종료 시간 (HH:mm) 또는 null
+ isAllDay: boolean; // 종일 여부
+ type: CalendarScheduleType; // 일정 타입
+ department: string | null; // 부서명
+ personName: string | null; // 담당자명
+ color: string | null; // 일정 색상
+}
+
+/** GET /api/proxy/calendar/schedules 응답 */
+export interface CalendarApiResponse {
+ items: CalendarScheduleItemApiResponse[];
+ total_count: number; // 총 일정 수
+}
+
+// ============================================
+// 9. Vat (부가세) API 응답 타입
+// ============================================
+
+/** 부가세 금액 카드 아이템 */
+export interface VatAmountCardApiResponse {
+ id: string; // 카드 ID (vat_sales_tax, vat_purchases_tax, etc.)
+ label: string; // 카드 라벨
+ amount: number; // 금액 또는 건수
+ subLabel?: string; // 부가 라벨 (예: "환급")
+ unit?: string; // 단위 (예: "건")
+}
+
+/** 부가세 체크포인트 하이라이트 아이템 */
+export interface VatHighlightItemApiResponse {
+ text: string; // 하이라이트 텍스트
+ color: string; // 색상 (red, blue, green 등)
+}
+
+/** 부가세 체크포인트 아이템 */
+export interface VatCheckPointApiResponse {
+ id: string; // 체크포인트 ID
+ type: string; // 타입 (success, warning, error)
+ message: string; // 메시지
+ highlights?: VatHighlightItemApiResponse[]; // 하이라이트 아이템 목록
+}
+
+/** GET /api/proxy/vat/summary 응답 */
+export interface VatApiResponse {
+ cards: VatAmountCardApiResponse[];
+ check_points: VatCheckPointApiResponse[];
+}
+
+// ============================================
+// 10. Entertainment (접대비) API 응답 타입
+// ============================================
+
+/** 접대비 금액 카드 아이템 */
+export interface EntertainmentAmountCardApiResponse {
+ id: string; // 카드 ID (et_sales, et_limit, etc.)
+ label: string; // 카드 라벨
+ amount: number; // 금액
+ subLabel?: string; // 부가 라벨
+ unit?: string; // 단위
+}
+
+/** 접대비 체크포인트 하이라이트 아이템 */
+export interface EntertainmentHighlightItemApiResponse {
+ text: string; // 하이라이트 텍스트
+ color: string; // 색상 (red, green, orange 등)
+}
+
+/** 접대비 체크포인트 아이템 */
+export interface EntertainmentCheckPointApiResponse {
+ id: string; // 체크포인트 ID
+ type: string; // 타입 (success, warning, error)
+ message: string; // 메시지
+ highlights?: EntertainmentHighlightItemApiResponse[]; // 하이라이트 아이템 목록
+}
+
+/** GET /api/proxy/entertainment/summary 응답 */
+export interface EntertainmentApiResponse {
+ cards: EntertainmentAmountCardApiResponse[];
+ check_points: EntertainmentCheckPointApiResponse[];
+}
+
+// ============================================
+// 11. Welfare (복리후생비) API 응답 타입
+// ============================================
+
+/** 복리후생비 금액 카드 아이템 */
+export interface WelfareAmountCardApiResponse {
+ id: string; // 카드 ID (wf_annual_limit, wf_period_limit, etc.)
+ label: string; // 카드 라벨
+ amount: number; // 금액
+ subLabel?: string; // 부가 라벨
+ unit?: string; // 단위
+}
+
+/** 복리후생비 체크포인트 하이라이트 아이템 */
+export interface WelfareHighlightItemApiResponse {
+ text: string; // 하이라이트 텍스트
+ color: string; // 색상 (red, green, orange 등)
+}
+
+/** 복리후생비 체크포인트 아이템 */
+export interface WelfareCheckPointApiResponse {
+ id: string; // 체크포인트 ID
+ type: string; // 타입 (success, warning, error)
+ message: string; // 메시지
+ highlights?: WelfareHighlightItemApiResponse[]; // 하이라이트 아이템 목록
+}
+
+/** GET /api/proxy/welfare/summary 응답 */
+export interface WelfareApiResponse {
+ cards: WelfareAmountCardApiResponse[];
+ check_points: WelfareCheckPointApiResponse[];
+}
\ No newline at end of file