diff --git a/src/components/business/CEODashboard/CEODashboard.tsx b/src/components/business/CEODashboard/CEODashboard.tsx index 05940b6e..f2ec3373 100644 --- a/src/components/business/CEODashboard/CEODashboard.tsx +++ b/src/components/business/CEODashboard/CEODashboard.tsx @@ -41,17 +41,23 @@ import { getCardManagementModalConfigWithData } from './modalConfigs'; import { transformEntertainmentDetailResponse, transformWelfareDetailResponse, transformVatDetailResponse } from '@/lib/api/dashboard/transformers'; import { toast } from 'sonner'; import { consumeStaleSections, DASHBOARD_INVALIDATE_EVENT, type DashboardSectionKey } from '@/lib/dashboard-invalidation'; +import { useModules } from '@/hooks/useModules'; +import { sectionRequiresModule } from './types'; export function CEODashboard() { const router = useRouter(); - // API 데이터 Hook + // 모듈 활성화 정보 (tenantIndustry 미설정 시 모든 모듈 표시) + const { isEnabled, tenantIndustry } = useModules(); + const moduleAware = !!tenantIndustry; // industry 설정 시에만 모듈 필터링 적용 + + // API 데이터 Hook (모듈 비활성 시 API 호출 스킵) const apiData = useCEODashboard({ salesStatus: true, purchaseStatus: true, - dailyProduction: true, - unshipped: true, - construction: true, + dailyProduction: !moduleAware || isEnabled('production'), + unshipped: true, // 공통 (outbound/logistics) + construction: !moduleAware || isEnabled('construction'), dailyAttendance: true, }); @@ -548,8 +554,16 @@ export function CEODashboard() { } }, [calendarData]); - // 섹션 순서 - const sectionOrder = dashboardSettings.sectionOrder ?? DEFAULT_SECTION_ORDER; + // 섹션 순서 (모듈 비활성 섹션 필터링) + const sectionOrder = useMemo(() => { + const rawOrder = dashboardSettings.sectionOrder ?? DEFAULT_SECTION_ORDER; + if (!moduleAware) return rawOrder; // industry 미설정 시 전부 표시 + return rawOrder.filter((key) => { + const requiredModule = sectionRequiresModule(key); + if (!requiredModule) return true; // 공통 섹션 + return isEnabled(requiredModule); + }); + }, [dashboardSettings.sectionOrder, moduleAware, isEnabled]); // 요약 네비게이션 바 훅 const { summaries, activeSectionKey, sectionRefs, scrollToSection } = useSectionSummary({ diff --git a/src/components/business/CEODashboard/dialogs/DashboardSettingsDialog.tsx b/src/components/business/CEODashboard/dialogs/DashboardSettingsDialog.tsx index cccd950f..34614f8a 100644 --- a/src/components/business/CEODashboard/dialogs/DashboardSettingsDialog.tsx +++ b/src/components/business/CEODashboard/dialogs/DashboardSettingsDialog.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useCallback, useEffect } from 'react'; +import { useState, useCallback, useEffect, useMemo } from 'react'; import { Button } from '@/components/ui/button'; import { Dialog, @@ -19,7 +19,8 @@ import type { WelfareCalculationType, SectionKey, } from '../types'; -import { DEFAULT_SECTION_ORDER, SECTION_LABELS } from '../types'; +import { DEFAULT_SECTION_ORDER, SECTION_LABELS, sectionRequiresModule } from '../types'; +import { useModules } from '@/hooks/useModules'; import { SectionRow, StatusBoardItemsList, @@ -40,6 +41,10 @@ export function DashboardSettingsDialog({ settings, onSave, }: DashboardSettingsDialogProps) { + // 모듈 활성화 정보 (industry 미설정 시 전부 표시) + const { isEnabled, tenantIndustry } = useModules(); + const moduleAware = !!tenantIndustry; + const [localSettings, setLocalSettings] = useState(settings); const [expandedSections, setExpandedSections] = useState>({ todayIssueList: false, @@ -53,8 +58,16 @@ export function DashboardSettingsDialog({ const [draggedSection, setDraggedSection] = useState(null); const [dragOverSection, setDragOverSection] = useState(null); - // 섹션 순서 - const sectionOrder = localSettings.sectionOrder ?? DEFAULT_SECTION_ORDER; + // 섹션 순서 (모듈 비활성 섹션 숨김) + const sectionOrder = useMemo(() => { + const rawOrder = localSettings.sectionOrder ?? DEFAULT_SECTION_ORDER; + if (!moduleAware) return rawOrder; + return rawOrder.filter((key: SectionKey) => { + const requiredModule = sectionRequiresModule(key); + if (!requiredModule) return true; + return isEnabled(requiredModule); + }); + }, [localSettings.sectionOrder, moduleAware, isEnabled]); // settings가 변경될 때 로컬 상태 업데이트 useEffect(() => { diff --git a/src/components/business/CEODashboard/sections/CalendarSection.tsx b/src/components/business/CEODashboard/sections/CalendarSection.tsx index ba010adb..8b6f003e 100644 --- a/src/components/business/CEODashboard/sections/CalendarSection.tsx +++ b/src/components/business/CEODashboard/sections/CalendarSection.tsx @@ -16,6 +16,7 @@ import { ScheduleCalendar } from '@/components/common/ScheduleCalendar'; import type { ScheduleEvent } from '@/components/common/ScheduleCalendar/types'; import { getCalendarEventsForYear, type CalendarEvent } from '@/constants/calendarEvents'; import { useCalendarScheduleStore } from '@/stores/useCalendarScheduleStore'; +import { useModules } from '@/hooks/useModules'; import { CollapsibleDashboardCard } from '../components'; import type { CalendarScheduleItem, @@ -117,6 +118,12 @@ const TASK_FILTER_OPTIONS: { value: ExtendedTaskFilterType; label: string }[] = { value: 'issue', label: '이슈' }, ]; +// 일정 타입 → 모듈 매핑 (이 타입의 링크/필터가 해당 모듈을 요구) +const SCHEDULE_TYPE_MODULE: Record = { + order: 'production', + construction: 'construction', +}; + export function CalendarSection({ schedules, issues = [], @@ -124,12 +131,24 @@ export function CalendarSection({ onScheduleEdit, }: CalendarSectionProps) { const router = useRouter(); + const { isEnabled, tenantIndustry } = useModules(); + const moduleAware = !!tenantIndustry; const [selectedDate, setSelectedDate] = useState(new Date()); const [currentDate, setCurrentDate] = useState(new Date()); const [, _setViewType] = useState('month'); const [deptFilter, setDeptFilter] = useState('all'); const [taskFilter, setTaskFilter] = useState('all'); + // 모듈 기반 업무 필터 옵션 (비활성 모듈 필터 숨김) + const filteredTaskFilterOptions = useMemo(() => { + if (!moduleAware) return TASK_FILTER_OPTIONS; + return TASK_FILTER_OPTIONS.filter((option) => { + const requiredModule = SCHEDULE_TYPE_MODULE[option.value]; + if (!requiredModule) return true; + return isEnabled(requiredModule as 'production' | 'construction'); + }); + }, [moduleAware, isEnabled]); + // 스토어에서 공휴일/세무일정 가져오기 (API 연동) const schedulesByYear = useCalendarScheduleStore((s) => s.schedulesByYear); const fetchSchedules = useCalendarScheduleStore((s) => s.fetchSchedules); @@ -272,7 +291,13 @@ export function CalendarSection({ }; // 일정 타입별 상세 페이지 링크 생성 (bill_123 → /ko/accounting/bills/123) + // 모듈 비활성 시 해당 타입의 링크 숨김 const getScheduleLink = (schedule: CalendarScheduleItem): string | null => { + // 모듈 의존 타입인데 해당 모듈이 비활성이면 링크 없음 + const requiredModule = SCHEDULE_TYPE_MODULE[schedule.type]; + if (moduleAware && requiredModule && !isEnabled(requiredModule as 'production' | 'construction')) { + return null; + } const basePath = SCHEDULE_TYPE_ROUTES[schedule.type]; if (!basePath) return null; // expected_expense는 목록 페이지만 존재 (상세 페이지 없음) @@ -383,7 +408,7 @@ export function CalendarSection({ - {TASK_FILTER_OPTIONS.map((option) => ( + {filteredTaskFilterOptions.map((option) => ( {option.label} @@ -432,7 +457,7 @@ export function CalendarSection({ - {TASK_FILTER_OPTIONS.map((option) => ( + {filteredTaskFilterOptions.map((option) => ( {option.label} diff --git a/src/components/business/CEODashboard/types.ts b/src/components/business/CEODashboard/types.ts index 44b34bd9..790d072c 100644 --- a/src/components/business/CEODashboard/types.ts +++ b/src/components/business/CEODashboard/types.ts @@ -3,6 +3,7 @@ */ import type React from 'react'; +import type { ModuleId } from '@/modules/types'; // 체크포인트 타입 (경고/성공/에러/정보) export type CheckPointType = 'success' | 'warning' | 'error' | 'info'; @@ -716,6 +717,21 @@ export interface DetailModalConfig { table?: TableConfig; } +// ===== 모듈별 섹션 매핑 (Phase 2: Dashboard Decoupling) ===== + +/** 특정 모듈이 필요한 섹션 매핑 (여기 없는 섹션은 공통 = 항상 표시) */ +export const MODULE_DEPENDENT_SECTIONS: Partial> = { + production: 'production', + shipment: 'production', + construction: 'construction', + // unshipped는 공통(outbound/logistics) — 모듈 의존성 없음 +}; + +/** 섹션이 요구하는 모듈 ID 반환. 공통 섹션이면 null */ +export function sectionRequiresModule(sectionKey: SectionKey): ModuleId | null { + return MODULE_DEPENDENT_SECTIONS[sectionKey] ?? null; +} + // 기본 설정값 export const DEFAULT_DASHBOARD_SETTINGS: DashboardSettings = { // 새 오늘의 이슈 (리스트 형태) diff --git a/src/components/business/CEODashboard/useSectionSummary.ts b/src/components/business/CEODashboard/useSectionSummary.ts index bfe8f1a2..dd0f20e8 100644 --- a/src/components/business/CEODashboard/useSectionSummary.ts +++ b/src/components/business/CEODashboard/useSectionSummary.ts @@ -2,7 +2,8 @@ import { useMemo, useEffect, useState, useRef, useCallback } from 'react'; import type { CEODashboardData, DashboardSettings, SectionKey } from './types'; -import { SECTION_LABELS } from './types'; +import { SECTION_LABELS, sectionRequiresModule } from './types'; +import { useModules } from '@/hooks/useModules'; export type SummaryStatus = 'normal' | 'warning' | 'danger'; @@ -220,10 +221,21 @@ export function useSectionSummary({ // 칩 클릭으로 선택된 키 — 해당 섹션이 화면에 보이는 한 유지 const pinnedKey = useRef(null); - // 활성화된 섹션만 필터 + // 모듈 활성화 정보 (tenantIndustry 미설정 시 전부 표시) + const { isEnabled, tenantIndustry } = useModules(); + const moduleAware = !!tenantIndustry; + + // 활성화된 섹션만 필터 (설정 + 모듈) const enabledSections = useMemo( - () => sectionOrder.filter((key) => isSectionEnabled(key, dashboardSettings)), - [sectionOrder, dashboardSettings], + () => sectionOrder.filter((key) => { + if (!isSectionEnabled(key, dashboardSettings)) return false; + if (moduleAware) { + const requiredModule = sectionRequiresModule(key); + if (requiredModule && !isEnabled(requiredModule)) return false; + } + return true; + }), + [sectionOrder, dashboardSettings, moduleAware, isEnabled], ); // 요약 데이터 계산