## 핵심 변경
- auth_perm_tree를 메뉴 SSOT로 확장 (V020~V024)
- url_path, label_key, component_key, nav_group, nav_sub_group, nav_sort 컬럼
- labels JSONB (다국어: {"ko":"...", "en":"..."})
- 보이지 않는 도메인 그룹 8개 삭제 (surveillance, detection, risk-assessment 등)
- 권한 트리 = 메뉴 트리 완전 동기화
- 그룹 레벨 권한 → 개별 자식 권한으로 확장 후 그룹 삭제
- 패널 노드 parent_cd를 실제 소속 페이지로 수정
(어구식별→어구탐지, 전역제외→후보제외, 역할관리→권한관리)
- vessel:vessel-detail 권한 노드 제거 (드릴다운 전용, 인증만 체크)
## 백엔드
- MenuConfigService: auth_perm_tree에서 menuConfig DTO 생성
- /api/auth/me 응답에 menuConfig 포함 (로그인 시 프리로드)
- @RequirePermission 12곳 수정 (삭제된 그룹명 → 구체적 자식 리소스)
- Caffeine 캐시 menuConfig 추가
## 프론트엔드
- NAV_ENTRIES 하드코딩 제거 → menuStore(Zustand) 동적 렌더링
- PATH_TO_RESOURCE 하드코딩 제거 → DB 기반 longest-match
- App.tsx 36개 정적 import/33개 Route → DynamicRoutes + componentRegistry
- PermissionsPanel: DB labels JSONB 기반 표시명 + 페이지/패널 아이콘 구분
- DB migration README.md 전면 재작성 (V001~V024, 49테이블, 149인덱스)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
137 lines
5.4 KiB
TypeScript
137 lines
5.4 KiB
TypeScript
import { lazy, type ComponentType } from 'react';
|
|
|
|
type LazyComponent = React.LazyExoticComponent<ComponentType<unknown>>;
|
|
|
|
/**
|
|
* DB menu_config.component_key → lazy-loaded React 컴포넌트 매핑.
|
|
* 메뉴 추가 시 이 레지스트리에 1줄만 추가하면 됨.
|
|
*/
|
|
export const COMPONENT_REGISTRY: Record<string, LazyComponent> = {
|
|
// ── 상황판·감시 ──
|
|
'features/dashboard/Dashboard': lazy(() =>
|
|
import('@features/dashboard/Dashboard').then((m) => ({ default: m.Dashboard })),
|
|
),
|
|
'features/monitoring/MonitoringDashboard': lazy(() =>
|
|
import('@features/monitoring/MonitoringDashboard').then((m) => ({
|
|
default: m.MonitoringDashboard,
|
|
})),
|
|
),
|
|
'features/surveillance/LiveMapView': lazy(() =>
|
|
import('@features/surveillance').then((m) => ({ default: m.LiveMapView })),
|
|
),
|
|
'features/surveillance/MapControl': lazy(() =>
|
|
import('@features/surveillance').then((m) => ({ default: m.MapControl })),
|
|
),
|
|
// ── 위험도·단속 ──
|
|
'features/risk-assessment/RiskMap': lazy(() =>
|
|
import('@features/risk-assessment').then((m) => ({ default: m.RiskMap })),
|
|
),
|
|
'features/risk-assessment/EnforcementPlan': lazy(() =>
|
|
import('@features/risk-assessment').then((m) => ({ default: m.EnforcementPlan })),
|
|
),
|
|
// ── 탐지 ──
|
|
'features/detection/DarkVesselDetection': lazy(() =>
|
|
import('@features/detection').then((m) => ({ default: m.DarkVesselDetection })),
|
|
),
|
|
'features/detection/GearDetection': lazy(() =>
|
|
import('@features/detection').then((m) => ({ default: m.GearDetection })),
|
|
),
|
|
'features/detection/ChinaFishing': lazy(() =>
|
|
import('@features/detection').then((m) => ({ default: m.ChinaFishing })),
|
|
),
|
|
// ── 단속·이벤트 ──
|
|
'features/enforcement/EnforcementHistory': lazy(() =>
|
|
import('@features/enforcement').then((m) => ({ default: m.EnforcementHistory })),
|
|
),
|
|
'features/enforcement/EventList': lazy(() =>
|
|
import('@features/enforcement').then((m) => ({ default: m.EventList })),
|
|
),
|
|
// ── 통계 ──
|
|
'features/statistics/Statistics': lazy(() =>
|
|
import('@features/statistics').then((m) => ({ default: m.Statistics })),
|
|
),
|
|
'features/statistics/ReportManagement': lazy(() =>
|
|
import('@features/statistics').then((m) => ({ default: m.ReportManagement })),
|
|
),
|
|
'features/statistics/ExternalService': lazy(() =>
|
|
import('@features/statistics').then((m) => ({ default: m.ExternalService })),
|
|
),
|
|
// ── 순찰 ──
|
|
'features/patrol/PatrolRoute': lazy(() =>
|
|
import('@features/patrol').then((m) => ({ default: m.PatrolRoute })),
|
|
),
|
|
'features/patrol/FleetOptimization': lazy(() =>
|
|
import('@features/patrol').then((m) => ({ default: m.FleetOptimization })),
|
|
),
|
|
// ── 현장작전 ──
|
|
'features/field-ops/AIAlert': lazy(() =>
|
|
import('@features/field-ops').then((m) => ({ default: m.AIAlert })),
|
|
),
|
|
'features/field-ops/MobileService': lazy(() =>
|
|
import('@features/field-ops').then((m) => ({ default: m.MobileService })),
|
|
),
|
|
'features/field-ops/ShipAgent': lazy(() =>
|
|
import('@features/field-ops').then((m) => ({ default: m.ShipAgent })),
|
|
),
|
|
// ── AI 운영 ──
|
|
'features/ai-operations/AIModelManagement': lazy(() =>
|
|
import('@features/ai-operations').then((m) => ({ default: m.AIModelManagement })),
|
|
),
|
|
'features/ai-operations/MLOpsPage': lazy(() =>
|
|
import('@features/ai-operations').then((m) => ({ default: m.MLOpsPage })),
|
|
),
|
|
'features/ai-operations/LLMOpsPage': lazy(() =>
|
|
import('@features/ai-operations').then((m) => ({ default: m.LLMOpsPage })),
|
|
),
|
|
'features/ai-operations/AIAssistant': lazy(() =>
|
|
import('@features/ai-operations').then((m) => ({ default: m.AIAssistant })),
|
|
),
|
|
// ── 관리 ──
|
|
'features/admin/AdminPanel': lazy(() =>
|
|
import('@features/admin').then((m) => ({ default: m.AdminPanel })),
|
|
),
|
|
'features/admin/SystemConfig': lazy(() =>
|
|
import('@features/admin').then((m) => ({ default: m.SystemConfig })),
|
|
),
|
|
'features/admin/DataHub': lazy(() =>
|
|
import('@features/admin').then((m) => ({ default: m.DataHub })),
|
|
),
|
|
'features/admin/AccessControl': lazy(() =>
|
|
import('@features/admin').then((m) => ({ default: m.AccessControl })),
|
|
),
|
|
'features/admin/NoticeManagement': lazy(() =>
|
|
import('@features/admin').then((m) => ({ default: m.NoticeManagement })),
|
|
),
|
|
'features/admin/AuditLogs': lazy(() =>
|
|
import('@features/admin/AuditLogs').then((m) => ({ default: m.AuditLogs })),
|
|
),
|
|
'features/admin/AccessLogs': lazy(() =>
|
|
import('@features/admin/AccessLogs').then((m) => ({ default: m.AccessLogs })),
|
|
),
|
|
'features/admin/LoginHistoryView': lazy(() =>
|
|
import('@features/admin/LoginHistoryView').then((m) => ({
|
|
default: m.LoginHistoryView,
|
|
})),
|
|
),
|
|
// ── 모선 워크플로우 ──
|
|
'features/parent-inference/ParentReview': lazy(() =>
|
|
import('@features/parent-inference/ParentReview').then((m) => ({
|
|
default: m.ParentReview,
|
|
})),
|
|
),
|
|
'features/parent-inference/ParentExclusion': lazy(() =>
|
|
import('@features/parent-inference/ParentExclusion').then((m) => ({
|
|
default: m.ParentExclusion,
|
|
})),
|
|
),
|
|
'features/parent-inference/LabelSession': lazy(() =>
|
|
import('@features/parent-inference/LabelSession').then((m) => ({
|
|
default: m.LabelSession,
|
|
})),
|
|
),
|
|
// ── 선박 (숨김 라우트) ──
|
|
'features/vessel/VesselDetail': lazy(() =>
|
|
import('@features/vessel').then((m) => ({ default: m.VesselDetail })),
|
|
),
|
|
};
|