# KCG AI Monitoring - 아키텍처 문서 > AI 기반 불법조업 탐지 차단 플랫폼 프론트엔드 --- ## 기술스택 | 분류 | 라이브러리 | 버전 | 역할 | |------|-----------|------|------| | UI 프레임워크 | React | 19.2.4 | 함수형 컴포넌트 + Hooks 기반 SPA | | 언어 | TypeScript | 5.9 | 정적 타입, strict 모드 | | 빌드 도구 | Vite | 8.0.3 | Rolldown 기반 ESM 번들링 (~480ms build), HMR | | CSS 프레임워크 | Tailwind CSS | 4.2.2 | `@tailwindcss/vite` 플러그인 통합 | | 지도 (베이스맵) | MapLibre GL | 5.22 | CartoDB 래스터 타일 (Dark/Light 자동 전환) | | 지도 (벡터) | deck.gl | 9.2 | MapboxOverlay 기반 GPU 벡터 렌더링 (40만척+) | | 차트 | ECharts | 6.0 (core) | `lib/charts` 래퍼를 통한 프리셋 차트 | | 상태관리 | Zustand | 5.0 | 8개 독립 스토어 (vessel, patrol, event, kpi 등) | | 라우팅 | react-router-dom | 7.12.0 | BrowserRouter, 중첩 Route | | 다국어 | react-i18next + i18next | 17.0 + 26.0 | 10 NS, ko/en 네임스페이스 기반 | | 스타일 변형 | class-variance-authority (CVA) | 0.7 | card/badge/statusDot variants | | 아이콘 | lucide-react | 0.487.0 | SVG 아이콘 컴포넌트 | | 린트 | ESLint | 10.2 | Flat Config, typescript-eslint + react-hooks + react-refresh | --- ## 디렉토리 구조 ``` src/ ├── lib/ │ ├── charts/ # BaseChart + 4 프리셋 (Area, Bar, Pie, Line) │ │ ├── BaseChart.tsx # 코어 (init, resize, dispose 자동 관리, 'kcg-dark' 테마) │ │ ├── theme.ts # ECharts 테마 등록 │ │ ├── tokens.ts # 차트 색상 토큰 │ │ └── presets/ # AreaChart, BarChart, PieChart, LineChart │ ├── map/ │ │ ├── BaseMap.tsx # MapLibre + deck.gl (forwardRef, memo, overlay 노출) │ │ ├── hooks/ # useMapLayers, useStoreLayerSync (RAF 기반) │ │ ├── layers/ # markers, polyline, heatmap, zones, static (STATIC_LAYERS) │ │ │ ├── markers.ts # createMarkerLayer (transitions + DataFilterExtension) │ │ │ ├── static.ts # EEZ + NLL 싱글턴 (GPU 1회 업로드) │ │ │ ├── polyline.ts # 경로/트랙 라인 │ │ │ ├── heatmap.ts # 히트맵 (위험도 시각화) │ │ │ ├── zones.ts # 구역 원 │ │ │ └── boundaries.ts # 레거시 GeoJSON (하위 호환) │ │ ├── controls/ # (예약) 지도 컨트롤 확장 │ │ ├── constants.ts # EEZ, NLL, 타일 URL, 기본값 │ │ └── types.ts # MapVessel, MapLayerConfig, HeatPoint │ ├── i18n/ # 10 NS (common, dashboard, detection, patrol, enforcement, statistics, ai, fieldOps, admin, auth) │ │ ├── config.ts # i18next 초기화 (ko 기본, en 폴백) │ │ └── locales/ # ko/*.json, en/*.json (10파일 x 2언어) │ │ # 2026-04-17: common.json 에 aria(36)/error(7)/dialog(4)/success(2)/message(5) 네임스페이스 추가 │ └── theme/ # tokens, colors, variants (CVA) │ ├── tokens.ts # CSS 변수 매핑 + resolved 색상값 │ ├── colors.ts # 시맨틱 팔레트 (risk, alert, vessel, status, chartSeries) │ └── variants.ts # cardVariants, badgeVariants, statusDotVariants (CVA) │ ├── data/ │ ├── mock/ # 7 공유 mock 모듈 │ │ ├── vessels.ts # 선박 목록 (한국, 중국, 경비함) │ │ ├── events.ts # 탐지/단속 이벤트 │ │ ├── transfers.ts # 전재(환적) 데이터 │ │ ├── patrols.ts # 순찰 경로/일정 │ │ ├── gear.ts # 어구 탐지 데이터 │ │ ├── kpi.ts # KPI/통계 데이터 │ │ └── enforcement.ts # 단속 이력 데이터 │ ├── areasCodes.json # 해역 코드 (52건) │ ├── speciesCodes.json # 어종 코드 (578건) │ ├── fisheryCodes.json # 어업유형 코드 (59건) │ ├── vesselTypeCodes.json # 선박유형 코드 (186건) │ └── commonCodes.ts # 코드 유틸리티 │ ├── stores/ # 8 Zustand 스토어 │ ├── vesselStore.ts # 선박 목록, 선택, 필터 │ ├── patrolStore.ts # 순찰 경로/함정 │ ├── eventStore.ts # 탐지/경보 이벤트 │ ├── kpiStore.ts # KPI 메트릭, 추세 │ ├── transferStore.ts # 전재(환적) 데이터 │ ├── gearStore.ts # 어구 탐지 │ ├── enforcementStore.ts # 단속 이력 │ └── settingsStore.ts # theme/language + localStorage 동기화 │ ├── services/ # 7 API 서비스 (현재 mock 반환) │ ├── api.ts # fetch 래퍼 (향후 Axios 교체 예정) │ ├── vessel.ts # getVessels, getSuspects, getVesselDetail │ ├── event.ts # getEvents, getAlerts │ ├── patrol.ts # getPatrolShips │ ├── kpi.ts # getKpiMetrics, getMonthlyTrends, getViolationTypes │ ├── ws.ts # connectWs (STOMP 스텁, 미구현) │ └── index.ts # 배럴 export │ ├── shared/components/ # 공유 UI 컴포넌트 (design-system.html SSOT) │ ├── ui/ # 9개 공통 컴포넌트 (2026-04-17 모든 화면 SSOT 준수 완료) │ │ ├── card.tsx # Card(CVA variant), CardHeader, CardTitle, CardContent (4 variant) │ │ ├── badge.tsx # Badge(CVA intent 8종 × size 4단계, LEGACY_MAP 변형 호환) │ │ ├── button.tsx # Button (variant 5종 × size 3단계, icon/trailingIcon prop) │ │ ├── input.tsx # Input (size/state, forwardRef) │ │ ├── select.tsx # Select (aria-label|aria-labelledby|title TS union 강제) │ │ ├── textarea.tsx # Textarea │ │ ├── checkbox.tsx # Checkbox (native input 래퍼) │ │ ├── radio.tsx # Radio │ │ └── tabs.tsx # TabBar + TabButton (underline/pill/segmented 3 variant) │ ├── layout/ # PageContainer / PageHeader / Section (표준 페이지 루트) │ └── common/ │ ├── DataTable.tsx # 범용 테이블 (가변너비, 검색, 정렬, 페이징, 엑셀, 출력) │ ├── Pagination.tsx # 페이지네이션 │ ├── SearchInput.tsx # 검색 입력 (i18n 통합) │ ├── ExcelExport.tsx # 엑셀 다운로드 │ ├── FileUpload.tsx # 파일 업로드 │ ├── PageToolbar.tsx # 페이지 상단 툴바 │ ├── PrintButton.tsx # 인쇄 버튼 │ ├── SaveButton.tsx # 저장 버튼 │ └── NotificationBanner.tsx # 알림 배너 (common.aria.closeNotification) │ ├── features/ # 13 도메인 그룹 (31 페이지) │ ├── dashboard/ # 종합 대시보드 (Dashboard) │ ├── monitoring/ # 실시간 모니터링 (MonitoringDashboard) │ ├── surveillance/ # 감시 (LiveMapView, MapControl) │ ├── detection/ # 탐지 (DarkVessel, Gear, ChinaFishing) │ ├── risk-assessment/ # 위험도 평가 (RiskMap, EnforcementPlan) │ ├── patrol/ # 순찰 (PatrolRoute, FleetOptimization) │ ├── enforcement/ # 단속 (EnforcementHistory, EventList) │ ├── statistics/ # 통계 (Statistics, ExternalService, ReportManagement) │ ├── ai-operations/ # AI 운영 (AIModelManagement, MLOps, AIAssistant) │ ├── field-ops/ # 현장 대응 (MobileService, ShipAgent, AIAlert) │ ├── admin/ # 관리 (AccessControl, SystemConfig, Notice, DataHub, AdminPanel) │ ├── vessel/ # 선박 (VesselDetail, TransferDetection) │ └── auth/ # 인증 (LoginPage) │ ├── app/ # 애플리케이션 셸 │ ├── App.tsx # BrowserRouter + Routes (26 보호 경로 + login) │ ├── auth/AuthContext.tsx # 인증 컨텍스트 (ProtectedRoute) │ └── layout/MainLayout.tsx # 사이드바 + 콘텐츠 + i18n 메뉴 │ └── styles/ # 전역 스타일 ├── theme.css # CSS 커스텀 속성 (dark 기본 + .light 오버라이드) ├── tailwind.css # Tailwind 기본 └── fonts.css # 웹폰트 ``` --- ## Path Alias | Alias | 경로 | 용도 | |-------|------|------| | `@/*` | `src/*` | 프로젝트 전체 절대 임포트 | | `@lib/*` | `src/lib/*` | 공통 라이브러리 (charts, map, i18n, theme) | | `@shared/*` | `src/shared/*` | 공유 UI 컴포넌트 | | `@features/*` | `src/features/*` | 도메인 feature 모듈 | | `@data/*` | `src/data/*` | 기준정보 + mock 데이터 | | `@stores/*` | `src/stores/*` | Zustand 스토어 | Vite `resolve.alias`와 TypeScript `compilerOptions.paths`에 동일하게 설정되어 있다. --- ## 의존성 ### 프로덕션 (11개) | 패키지 | 버전 | 용도 | |--------|------|------| | react | ^19.2.4 | UI 렌더링 | | react-dom | ^19.2.4 | DOM 렌더링 | | react-router-dom | ^7.12.0 | SPA 라우팅 | | maplibre-gl | ^5.22.0 | 래스터 베이스맵 엔진 | | deck.gl | ^9.2.11 | GPU 벡터 렌더링 (ScatterplotLayer, PathLayer 등) | | @deck.gl/mapbox | ^9.2.11 | MapboxOverlay (MapLibre 인터리브) | | echarts | ^6.0.0 | 차트 라이브러리 | | zustand | ^5.0.12 | 경량 상태관리 (8개 스토어) | | class-variance-authority | ^0.7.1 | Tailwind 변형 관리 (CVA) | | react-i18next | ^17.0.2 | React 다국어 바인딩 | | i18next | ^26.0.3 | 다국어 코어 | | lucide-react | 0.487.0 | SVG 아이콘 | ### 개발 (13개) | 패키지 | 버전 | 용도 | |--------|------|------| | vite | ^8.0.3 | 빌드/개발 서버 (Rolldown) | | @vitejs/plugin-react | ^6.0.1 | React Fast Refresh | | tailwindcss | ^4.2.2 | 유틸리티 CSS | | @tailwindcss/vite | ^4.2.2 | Tailwind Vite 플러그인 | | typescript | 5.9 | 타입 체크 | | eslint | ^10.2.0 | 린트 (Flat Config) | | @eslint/js | ^10.0.1 | ESLint JS 설정 | | typescript-eslint | ^8.58.0 | TS ESLint 파서/규칙 | | eslint-plugin-react-hooks | ^7.0.1 | Hooks 규칙 | | eslint-plugin-react-refresh | ^0.5.2 | Fast Refresh 규칙 | | globals | ^17.4.0 | 전역 변수 정의 | | @types/react | ^19.2.14 | React 타입 | | @types/react-dom | ^19.2.3 | ReactDOM 타입 | --- ## 공통 모듈 API 요약 ### lib/map — BaseMap + deck.gl 최적화 **BaseMap** — MapLibre + deck.gl 통합 컴포넌트 (`forwardRef`, `memo`) ```typescript interface BaseMapProps { center?: [number, number]; // [lat, lng] 기본값 [35.5, 127.0] zoom?: number; // 기본값 7 className?: string; style?: React.CSSProperties; height?: number | string; // 기본값 '100%' layers?: Layer[]; // @deprecated — useMapLayers hook 사용 권장 onMapReady?: (map: Map) => void; // 지도 로드 완료 콜백 onClick?: (info: unknown) => void; interactive?: boolean; // 기본 true } // ref를 통해 MapHandle 노출 interface MapHandle { overlay: MapboxOverlay | null; // deck.gl overlay 직접 접근 } ``` - `useImperativeHandle`로 overlay 외부 노출 → hook에서 직접 `setProps()` 호출 - CartoDB Dark/Light 래스터 타일 자동 전환 (`settingsStore.theme` 구독) - `interactive=false`로 정적 지도 프리뷰 생성 가능 **useMapLayers** — RAF 배치 레이어 업데이트 (React 리렌더 0회) ```typescript useMapLayers( handleRef: RefObject, // BaseMap ref buildLayers: () => Layer[], // 레이어 빌드 함수 deps: unknown[], // 변경 감지 (shallow 비교) ) ``` **useStoreLayerSync** — Zustand store.subscribe + RAF 기반 레이어 동기화 ```typescript useStoreLayerSync( handleRef: RefObject, subscribe: (cb: (state: T) => void) => () => void, buildLayers: (state: T) => Layer[], ) ``` **STATIC_LAYERS** — EEZ + NLL 싱글턴 (모듈 로드 시 1회 생성, GPU 재전송 없음) **createMarkerLayer** — ScatterplotLayer 생성 (transitions 보간 + DataFilterExtension) ### lib/charts — ECharts 래퍼 **BaseChart** — ECharts 코어 래퍼 컴포넌트 ```typescript interface BaseChartProps { option: EChartsOption; className?: string; style?: React.CSSProperties; height?: number; // 기본값 200 notMerge?: boolean; onEvents?: Record void>; } ``` - `echarts.init(container, 'kcg-dark')` 프로젝트 다크 테마 자동 적용 - `ResizeObserver` 자동 리사이즈, unmount 시 `dispose` 자동 정리 **프리셋 차트** | 컴포넌트 | 주요 Props | 설명 | |----------|-----------|------| | `AreaChart` | `data`, `xKey`, `series`, `yAxisDomain?` | smooth line + 반투명 영역 | | `BarChart` | `data`, `xKey`, `series`, `horizontal?`, `itemColors?` | 수직/수평 막대, 항목별 색상 | | `PieChart` | `data: {name, value, color?}[]`, `innerRadius?`, `outerRadius?` | 파이/도넛 (기본 도넛) | | `LineChart` | `data`, `xKey`, `series` | smooth 라인, 원형 심볼 | ### lib/theme — CVA 변형 + 디자인 토큰 | 모듈 | 내용 | |------|------| | `variants.ts` | `cardVariants` (default/elevated/inner/transparent), `badgeVariants` (8 intent x 4 size), `statusDotVariants` (4 status x 3 size) | | `tokens.ts` | `cssVars` (CSS 변수 참조), `resolvedColors` (ECharts/MapLibre용 하드코딩 값) | | `colors.ts` | `riskColors` (5단계), `alertStyles`, `vesselColors`, `statusColors`, `chartSeriesColors` (8색 팔레트) | ### lib/i18n — 10 네임스페이스 다국어 - `i18next` + `react-i18next` 기반 - 기본 언어: `ko`, 폴백: `ko`, 지원: `ko` / `en` - 10 네임스페이스: `common`, `dashboard`, `detection`, `patrol`, `enforcement`, `statistics`, `ai`, `fieldOps`, `admin`, `auth` - 사용: `useTranslation('namespace')` → `t('key')` - 언어 전환: `settingsStore.toggleLanguage()` + `localStorage` 동기화 --- ## 렌더링 최적화 아키텍처 ``` store 변경 → useStoreLayerSync → RAF → overlay.setProps() (React 리렌더 0회) deps 변경 → useMapLayers → RAF → overlay.setProps() (React 리렌더 0회) 정적 레이어 → STATIC_LAYERS 싱글턴 (GPU 1회 업로드, 모든 페이지 공유) 동적 레이어 → transitions 보간 + DataFilterExtension (GPU 필터링) 시계/타이머 → useRef + DOM textContent 직접 조작 (setState 0회) ``` 핵심 원칙: **React render cycle 완전 우회**. deck.gl overlay에 직접 `setProps()`를 호출하여 40만척+ 실시간 렌더링 시에도 React 리렌더가 발생하지 않는다. --- ## 테마 시스템 - **기본값**: `:root` = dark 테마, `.light` 클래스 오버라이드 - **시맨틱 CSS 변수**: `surface-raised`, `surface-overlay`, `text-heading`, `text-label`, `text-hint`, `border` - **Tailwind 통합**: `@theme inline`으로 CSS 변수를 Tailwind 유틸리티에 매핑 - **settingsStore**: `theme` / `language` + `localStorage` 자동 동기화 - **지도 타일**: CartoDB Dark Matter ↔ CartoDB Positron 자동 전환 (`settingsStore.theme` 구독) - **CVA 변형**: `cardVariants`, `badgeVariants`, `statusDotVariants`가 CSS 변수 참조 → 테마 자동 반응 --- ## 라우팅 구조 (26 보호 경로 + login) `App.tsx`에서 `BrowserRouter` > `AuthProvider` > `Routes`로 구성된다. - `/login` — 비보호 라우트 (LoginPage) - `/` — `ProtectedRoute` > `MainLayout` (사이드바 + Outlet) - `/` → `/dashboard` 리다이렉트 - `/dashboard` — 종합 대시보드 (SFR-12) - `/monitoring` — 실시간 모니터링 - `/risk-map` — 위험도 평가 (SFR-05) - `/enforcement-plan` — 단속계획 (SFR-06) - `/dark-vessel` — 무등화 선박 탐지 (SFR-09) - `/gear-detection` — 어구 탐지 (SFR-10) - `/china-fishing` — 중국어선 탐지 - `/patrol-route` — 순찰경로 (SFR-07) - `/fleet-optimization` — 함대 최적화 (SFR-08) - `/enforcement-history` — 단속 이력 (SFR-11) - `/event-list` — 이벤트 목록 - `/mobile-service` — 현장 모바일 (SFR-15) - `/ship-agent` — 함정 에이전트 (SFR-16) - `/ai-alert` — AI 경보 (SFR-17) - `/statistics` — 통계 (SFR-13) - `/external-service` — 외부연계 (SFR-14) - `/reports` — 보고서 관리 - `/ai-model` — AI 모델 관리 (SFR-04) - `/mlops` — MLOps (SFR-18~19) - `/ai-assistant` — AI 어시스턴트 (SFR-20) - `/data-hub` — 데이터허브 (SFR-03) - `/system-config` — 환경설정 (SFR-02) - `/notices` — 공지사항 - `/access-control` — 접근 권한 (SFR-01) - `/admin` — 시스템 관리 - `/events` — 감시 (LiveMapView) - `/map-control` — 지도 컨트롤 - `/vessel/:id` — 선박 상세 인증은 `AuthContext`의 `useAuth().user` 존재 여부로 판단하며, 미인증 시 `/login`으로 리다이렉트한다. --- ## 빌드 설정 - **TypeScript**: `target: ES2020`, `module: ESNext`, `moduleResolution: bundler`, `strict: true` - **Vite**: `react()` + `tailwindcss()` 플러그인, 6개 path alias (`@`, `@lib`, `@shared`, `@features`, `@data`, `@stores`) - **ESLint 10 Flat Config**: `typescript-eslint` + `react-hooks` + `react-refresh` 규칙 - **빌드 속도**: Rolldown 기반 ~480ms --- ## 현재 아키텍처 특성 1. **Zustand 8개 스토어**: vessel, patrol, event, kpi, transfer, gear, enforcement, settings. `settingsStore`는 theme/language + localStorage 동기화 담당. 2. **deck.gl GPU 렌더링**: MapLibre(래스터 베이스맵) + deck.gl(벡터). React 리렌더 완전 분리, RAF 기반 `overlay.setProps()` 직접 호출. 3. **CVA 스타일 시스템**: `cardVariants`, `badgeVariants`, `statusDotVariants`로 Tailwind 패턴 통합. CSS 변수 기반 테마 반응. 4. **mock 기반 서비스 계층**: 7개 API 서비스가 `data/mock/` 모듈에서 데이터 반환. 향후 Axios + 실제 API로 교체 예정. 5. **i18n 10 NS 구조 완성**: 리소스 파일 완비, MainLayout 메뉴 + 페이지 제목 + LoginPage 적용 완료. 페이지 내부 텍스트는 대부분 한국어 하드코딩 잔존. 6. **Dark/Light 테마**: CSS 변수 기반 양방향 테마. 지도 타일 자동 전환. 일부 alert 색상(`red-500/20` 등) 하드코딩 잔존. 7. **단일 번들**: 코드 스플리팅 미적용 (~3.2MB), React.lazy 미사용. 모든 feature가 단일 번들로 빌드. 8. **WebSocket 미구현**: `connectWs` 스텁만 존재, STOMP.js + SockJS 미설치.