Files
sam-react-prod/src/components/business/CEODashboard/CEODashboard.tsx
유병철 e5f0f5da61 feat(WEB): 차량 관리 기능 추가 및 CEO 대시보드 Enhanced 섹션 적용
차량 관리 (신규):
- VehicleList/VehicleDetail: 차량 목록/상세
- ForkliftList/ForkliftDetail: 지게차 목록/상세
- VehicleLogList/VehicleLogDetail: 운행일지 목록/상세
- 관련 페이지 라우트 추가 (/vehicle-management/*)

CEO 대시보드:
- Enhanced 섹션 컴포넌트 적용 (아이콘 + 컬러 테마)
- EnhancedStatusBoardSection, EnhancedDailyReportSection, EnhancedMonthlyExpenseSection
- TodayIssueSection 개선

IntegratedDetailTemplate:
- FieldInput, FieldRenderer 기능 확장

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 14:53:20 +09:00

406 lines
14 KiB
TypeScript

'use client';
import { useState, useCallback, useEffect, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { LayoutDashboard, Settings } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { CEODashboardSkeleton } from './skeletons';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import {
TodayIssueSection,
EnhancedStatusBoardSection,
EnhancedDailyReportSection,
EnhancedMonthlyExpenseSection,
CardManagementSection,
EntertainmentSection,
WelfareSection,
ReceivableSection,
DebtCollectionSection,
VatSection,
CalendarSection,
} from './sections';
import type { CEODashboardData, CalendarScheduleItem, DashboardSettings, DetailModalConfig } from './types';
import { DEFAULT_DASHBOARD_SETTINGS } from './types';
import { ScheduleDetailModal, DetailModal } from './modals';
import { DashboardSettingsDialog } from './dialogs/DashboardSettingsDialog';
import { mockData } from './mockData';
import { useCEODashboard, useTodayIssue, useCalendar, useVat, useEntertainment, useWelfare, useWelfareDetail, useMonthlyExpenseDetail } from '@/hooks/useCEODashboard';
import { useCardManagementModals, type CardManagementCardId } from '@/hooks/useCardManagementModals';
import type { MonthlyExpenseCardId } from '@/hooks/useCEODashboard';
import {
getMonthlyExpenseModalConfig,
getCardManagementModalConfig,
getCardManagementModalConfigWithData,
getEntertainmentModalConfig,
getWelfareModalConfig,
getVatModalConfig,
} from './modalConfigs';
export function CEODashboard() {
const router = useRouter();
// API 데이터 Hook (Phase 1 섹션들)
const apiData = useCEODashboard({
cardManagementFallback: mockData.cardManagement,
});
// TodayIssue API Hook (Phase 2)
const todayIssueData = useTodayIssue(30);
// Calendar API Hook (Phase 2)
const calendarData = useCalendar();
// Vat API Hook (Phase 2)
const vatData = useVat();
// Entertainment API Hook (Phase 2)
const entertainmentData = useEntertainment();
// Welfare API Hook (Phase 2)
const welfareData = useWelfare();
// Card Management Modal API Hook (Phase 3)
const cardManagementModals = useCardManagementModals();
// 전체 로딩 상태 (하나라도 로딩 중이면 스켈레톤 표시)
const isLoading = useMemo(() => {
return (
apiData.dailyReport.loading ||
apiData.receivable.loading ||
apiData.debtCollection.loading ||
apiData.monthlyExpense.loading ||
apiData.cardManagement.loading ||
apiData.statusBoard.loading ||
todayIssueData.loading ||
calendarData.loading ||
vatData.loading ||
entertainmentData.loading ||
welfareData.loading
);
}, [apiData, todayIssueData.loading, calendarData.loading, vatData.loading, entertainmentData.loading, welfareData.loading]);
// API 데이터와 mockData를 병합 (API 우선, 실패 시 fallback)
const data = useMemo<CEODashboardData>(() => ({
...mockData,
// Phase 1 섹션들: API 데이터 우선, 실패 시 mockData fallback
dailyReport: apiData.dailyReport.data ?? mockData.dailyReport,
receivable: apiData.receivable.data ?? mockData.receivable,
debtCollection: apiData.debtCollection.data ?? mockData.debtCollection,
monthlyExpense: apiData.monthlyExpense.data ?? mockData.monthlyExpense,
cardManagement: apiData.cardManagement.data ?? mockData.cardManagement,
// Phase 2 섹션들 (API 연동 완료 - 목업 fallback 제거)
todayIssue: apiData.statusBoard.data ?? [],
todayIssueList: todayIssueData.data?.items ?? [],
calendarSchedules: calendarData.data?.items ?? mockData.calendarSchedules,
vat: vatData.data ?? mockData.vat,
entertainment: entertainmentData.data ?? mockData.entertainment,
welfare: welfareData.data ?? mockData.welfare,
}), [apiData, todayIssueData.data, calendarData.data, vatData.data, entertainmentData.data, welfareData.data, mockData]);
// 일정 상세 모달 상태
const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false);
const [selectedSchedule, setSelectedSchedule] = useState<CalendarScheduleItem | null>(null);
// 항목 설정 모달 상태
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
const [dashboardSettings, setDashboardSettings] = useState<DashboardSettings>(DEFAULT_DASHBOARD_SETTINGS);
// WelfareDetail Hook (모달용 상세 API) - dashboardSettings 이후에 선언
const welfareDetailData = useWelfareDetail({
calculationType: dashboardSettings.welfare.calculationType,
});
// MonthlyExpenseDetail Hook (당월 예상 지출 모달용 상세 API)
const monthlyExpenseDetailData = useMonthlyExpenseDetail();
// 상세 모달 상태
const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);
const [detailModalConfig, setDetailModalConfig] = useState<DetailModalConfig | null>(null);
// 클라이언트에서만 localStorage에서 설정 불러오기 (hydration 에러 방지)
useEffect(() => {
const saved = localStorage.getItem('ceo-dashboard-settings');
if (saved) {
try {
setDashboardSettings(JSON.parse(saved));
} catch {
// 파싱 실패 시 기본값 유지
}
}
}, []);
// 항목 설정 클릭
const handleSettingClick = useCallback(() => {
setIsSettingsModalOpen(true);
}, []);
// 항목 설정 저장
const handleSettingsSave = useCallback((settings: DashboardSettings) => {
setDashboardSettings(settings);
// localStorage에 설정 저장
if (typeof window !== 'undefined') {
localStorage.setItem('ceo-dashboard-settings', JSON.stringify(settings));
}
}, []);
// 항목 설정 모달 닫기
const handleSettingsModalClose = useCallback(() => {
setIsSettingsModalOpen(false);
}, []);
// 일일 일보 클릭 → 일일 일보 페이지로 이동
const handleDailyReportClick = useCallback(() => {
router.push('/ko/accounting/daily-report');
}, [router]);
// 상세 모달 닫기
const handleDetailModalClose = useCallback(() => {
setIsDetailModalOpen(false);
setDetailModalConfig(null);
}, []);
// 당월 예상 지출 카드 클릭 (개별 카드 클릭 시 상세 모달)
const handleMonthlyExpenseCardClick = useCallback(async (cardId: string) => {
// 1. 먼저 API에서 데이터 fetch 시도
const apiConfig = await monthlyExpenseDetailData.fetchData(cardId as MonthlyExpenseCardId);
// 2. API 데이터가 있으면 사용, 없으면 fallback config 사용
const config = apiConfig ?? getMonthlyExpenseModalConfig(cardId);
if (config) {
setDetailModalConfig(config);
setIsDetailModalOpen(true);
}
}, [monthlyExpenseDetailData]);
// 당월 예상 지출 클릭 (deprecated - 개별 카드 클릭으로 대체)
const handleMonthlyExpenseClick = useCallback(() => {
console.log('당월 예상 지출 클릭');
}, []);
// 카드/가지급금 관리 카드 클릭 (개별 카드 클릭 시 상세 모달)
const handleCardManagementCardClick = useCallback(async (cardId: string) => {
// 1. API에서 데이터 fetch (데이터 직접 반환)
const modalData = await cardManagementModals.fetchModalData(cardId as CardManagementCardId);
// 2. API 데이터로 config 생성 (데이터 없으면 fallback)
const config = getCardManagementModalConfigWithData(cardId, modalData);
if (config) {
setDetailModalConfig(config);
setIsDetailModalOpen(true);
}
}, [cardManagementModals]);
// 접대비 현황 카드 클릭 (개별 카드 클릭 시 상세 모달)
const handleEntertainmentCardClick = useCallback((cardId: string) => {
const config = getEntertainmentModalConfig(cardId);
if (config) {
setDetailModalConfig(config);
setIsDetailModalOpen(true);
}
}, []);
// 복리후생비 현황 카드 클릭 (모든 카드가 동일한 상세 모달)
// 복리후생비 클릭 - API 데이터로 모달 열기 (fallback: 정적 config)
const handleWelfareCardClick = useCallback(async () => {
// 1. 먼저 API에서 데이터 fetch 시도
await welfareDetailData.refetch();
// 2. API 데이터가 있으면 사용, 없으면 fallback config 사용
const config = welfareDetailData.modalConfig ?? getWelfareModalConfig(dashboardSettings.welfare.calculationType);
setDetailModalConfig(config);
setIsDetailModalOpen(true);
}, [welfareDetailData, dashboardSettings.welfare.calculationType]);
// 부가세 클릭 (모든 카드가 동일한 상세 모달)
const handleVatClick = useCallback(() => {
const config = getVatModalConfig();
setDetailModalConfig(config);
setIsDetailModalOpen(true);
}, []);
// 캘린더 일정 클릭 (기존 일정 수정)
const handleScheduleClick = useCallback((schedule: CalendarScheduleItem) => {
setSelectedSchedule(schedule);
setIsScheduleModalOpen(true);
}, []);
// 캘린더 일정 등록 (새 일정)
const handleScheduleEdit = useCallback((schedule: CalendarScheduleItem) => {
setSelectedSchedule(schedule);
setIsScheduleModalOpen(true);
}, []);
// 일정 모달 닫기
const handleScheduleModalClose = useCallback(() => {
setIsScheduleModalOpen(false);
setSelectedSchedule(null);
}, []);
// 일정 저장
const handleScheduleSave = useCallback((formData: {
title: string;
department: string;
startDate: string;
endDate: string;
isAllDay: boolean;
startTime: string;
endTime: string;
color: string;
content: string;
}) => {
console.log('일정 저장:', formData);
// TODO: API 호출하여 일정 저장
setIsScheduleModalOpen(false);
setSelectedSchedule(null);
}, []);
// 일정 삭제
const handleScheduleDelete = useCallback((id: string) => {
console.log('일정 삭제:', id);
// TODO: API 호출하여 일정 삭제
setIsScheduleModalOpen(false);
setSelectedSchedule(null);
}, []);
if (isLoading) {
return (
<PageLayout>
<PageHeader
title="대시보드"
description="전체 현황을 조회합니다."
icon={LayoutDashboard}
/>
<CEODashboardSkeleton />
</PageLayout>
);
}
return (
<PageLayout>
<PageHeader
title="대시보드"
description="전체 현황을 조회합니다."
icon={LayoutDashboard}
actions={
<Button
variant="outline"
size="sm"
onClick={handleSettingClick}
className="gap-2"
>
<Settings className="h-4 w-4" />
</Button>
}
/>
<div className="space-y-6">
{/* 오늘의 이슈 (새 리스트 형태) */}
{dashboardSettings.todayIssueList && (
<TodayIssueSection items={data.todayIssueList} />
)}
{/* 일일 일보 (Enhanced) */}
{dashboardSettings.dailyReport && (
<EnhancedDailyReportSection
data={data.dailyReport}
onClick={handleDailyReportClick}
/>
)}
{/* 현황판 (Enhanced - 아이콘 + 컬러 테마) */}
{(dashboardSettings.statusBoard?.enabled ?? dashboardSettings.todayIssue.enabled) && (
<EnhancedStatusBoardSection
items={data.todayIssue}
itemSettings={dashboardSettings.statusBoard?.items ?? dashboardSettings.todayIssue.items}
/>
)}
{/* 당월 예상 지출 내역 (Enhanced) */}
{dashboardSettings.monthlyExpense && (
<EnhancedMonthlyExpenseSection
data={data.monthlyExpense}
onCardClick={handleMonthlyExpenseCardClick}
/>
)}
{/* 카드/가지급금 관리 */}
{dashboardSettings.cardManagement && (
<CardManagementSection
data={data.cardManagement}
onCardClick={handleCardManagementCardClick}
/>
)}
{/* 접대비 현황 */}
{dashboardSettings.entertainment.enabled && (
<EntertainmentSection
data={data.entertainment}
onCardClick={handleEntertainmentCardClick}
/>
)}
{/* 복리후생비 현황 */}
{dashboardSettings.welfare.enabled && (
<WelfareSection
data={data.welfare}
onCardClick={handleWelfareCardClick}
/>
)}
{/* 미수금 현황 */}
{dashboardSettings.receivable.enabled && (
<ReceivableSection data={data.receivable} />
)}
{/* 채권추심 현황 */}
{dashboardSettings.debtCollection && (
<DebtCollectionSection data={data.debtCollection} />
)}
{/* 부가세 현황 */}
{dashboardSettings.vat && (
<VatSection data={data.vat} onClick={handleVatClick} />
)}
{/* 캘린더 */}
{dashboardSettings.calendar && (
<CalendarSection
schedules={data.calendarSchedules}
issues={data.todayIssueList}
onScheduleClick={handleScheduleClick}
onScheduleEdit={handleScheduleEdit}
/>
)}
</div>
{/* 일정 상세 모달 */}
<ScheduleDetailModal
isOpen={isScheduleModalOpen}
onClose={handleScheduleModalClose}
schedule={selectedSchedule}
onSave={handleScheduleSave}
onDelete={handleScheduleDelete}
/>
{/* 항목 설정 모달 */}
<DashboardSettingsDialog
isOpen={isSettingsModalOpen}
onClose={handleSettingsModalClose}
settings={dashboardSettings}
onSave={handleSettingsSave}
/>
{/* 상세 모달 */}
{detailModalConfig && (
<DetailModal
isOpen={isDetailModalOpen}
onClose={handleDetailModalClose}
config={detailModalConfig}
/>
)}
</PageLayout>
);
}