KCG AI 기반 불법조업 탐지·차단 플랫폼 프론트엔드. React 19 + TypeScript 5.9 + Vite 8 + MapLibre + deck.gl + Zustand + Tailwind CSS. SFR 20개 전체 UI 구현 완료, 백엔드 연동 대기. - npm + Nexus 프록시 레지스트리 설정 - 팀 워크플로우 v1.6.1 부트스트랩 파일 배치 - .githooks (commit-msg, post-checkout) - package.json name: kcg-ai-monitoring v0.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
18 KiB
18 KiB
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언어)
│ └── 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 컴포넌트
│ ├── ui/
│ │ ├── card.tsx # Card(CVA variant), CardHeader, CardTitle, CardContent
│ │ └── badge.tsx # Badge(CVA intent/size)
│ └── common/
│ ├── DataTable.tsx # 범용 테이블 (가변너비, 검색, 정렬, 페이징, 엑셀, 출력)
│ ├── Pagination.tsx # 페이지네이션
│ ├── SearchInput.tsx # 검색 입력
│ ├── ExcelExport.tsx # 엑셀 다운로드
│ ├── FileUpload.tsx # 파일 업로드
│ ├── PageToolbar.tsx # 페이지 상단 툴바
│ ├── PrintButton.tsx # 인쇄 버튼
│ ├── SaveButton.tsx # 저장 버튼
│ └── NotificationBanner.tsx # 알림 배너
│
├── 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)
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회)
useMapLayers(
handleRef: RefObject<MapHandle>, // BaseMap ref
buildLayers: () => Layer[], // 레이어 빌드 함수
deps: unknown[], // 변경 감지 (shallow 비교)
)
useStoreLayerSync — Zustand store.subscribe + RAF 기반 레이어 동기화
useStoreLayerSync<T>(
handleRef: RefObject<MapHandle>,
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 코어 래퍼 컴포넌트
interface BaseChartProps {
option: EChartsOption;
className?: string;
style?: React.CSSProperties;
height?: number; // 기본값 200
notMerge?: boolean;
onEvents?: Record<string, (params: unknown) => 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
현재 아키텍처 특성
- Zustand 8개 스토어: vessel, patrol, event, kpi, transfer, gear, enforcement, settings.
settingsStore는 theme/language + localStorage 동기화 담당. - deck.gl GPU 렌더링: MapLibre(래스터 베이스맵) + deck.gl(벡터). React 리렌더 완전 분리, RAF 기반
overlay.setProps()직접 호출. - CVA 스타일 시스템:
cardVariants,badgeVariants,statusDotVariants로 Tailwind 패턴 통합. CSS 변수 기반 테마 반응. - mock 기반 서비스 계층: 7개 API 서비스가
data/mock/모듈에서 데이터 반환. 향후 Axios + 실제 API로 교체 예정. - i18n 10 NS 구조 완성: 리소스 파일 완비, MainLayout 메뉴 + 페이지 제목 + LoginPage 적용 완료. 페이지 내부 텍스트는 대부분 한국어 하드코딩 잔존.
- Dark/Light 테마: CSS 변수 기반 양방향 테마. 지도 타일 자동 전환. 일부 alert 색상(
red-500/20등) 하드코딩 잔존. - 단일 번들: 코드 스플리팅 미적용 (~3.2MB), React.lazy 미사용. 모든 feature가 단일 번들로 빌드.
- WebSocket 미구현:
connectWs스텁만 존재, STOMP.js + SockJS 미설치.