차량 관리 (신규): - 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>
406 lines
14 KiB
TypeScript
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>
|
|
);
|
|
} |