import { useState, useRef, useCallback } from 'react'; import { Card, CardContent } from '@shared/components/ui/card'; import { Badge } from '@shared/components/ui/badge'; import { Search, ChevronDown, ChevronUp, ChevronRight, Plus, X, Ship, AlertTriangle, Radar, Anchor, MapPin, Printer, Camera, Crosshair, Ruler, CircleDot, Clock, LayoutGrid, Brain } from 'lucide-react'; import { BaseMap, STATIC_LAYERS, createMarkerLayer, createZoneLayer, createPolylineLayer, JURISDICTION_AREAS, DEPTH_CONTOURS, useMapLayers, type MapHandle } from '@lib/map'; import type { MarkerData } from '@lib/map'; // TODO: 향후 store 통합 시 교체 — VesselDetail의 VesselTrack 형상(callSign, source, detail 등)이 // useVesselStore().vessels(VesselData)와 구조가 달라 현재는 인라인 데이터 유지 // ─── 선박 데이터 ────────────────────── interface VesselTrack { id: string; mmsi: string; callSign: string; source: string; name: string; type: string; country: string; detail: Record; } const VESSELS: VesselTrack[] = [ { id: '1', mmsi: '440162980', callSign: '122@', source: 'AIS', name: '504 FAREKIMHO', type: 'Fishing', country: 'Korea(Republic of)', detail: { '청코드': '부산', '호출부호': '951554', '입항횟수': '006', '전송구분': '최종', '선명': '태평양호', '선박종류': '어선', '총톤수': '30', '국제톤수': '30', '입항일시': '2023-03-28 16:00', '계선장소': '기타 남항 사설조선소', '전출항지': '2023-03-28 16:00', '전출항지항구명': '김천', '위험물톤수': '-', '외내항구분': '내항', '입항수리일자': '2023-03-24', '한국인선원수': '5', '외국인선원수': '9', '예선': 'N', '도선': 'N', }, }, { id: '2', mmsi: '440162923', callSign: '122@', source: 'AIS', name: 'ZZ', type: 'V-Pass', country: 'Korea(Republic of)', detail: { '청코드': '인천', '호출부호': '862331', '입항횟수': '012', '전송구분': '최종', '선명': '금강호', '선박종류': '어선', '총톤수': '45', '국제톤수': '45', '입항일시': '2023-04-15 09:00', '계선장소': '인천항 제2부두', '전출항지': '2023-04-15 09:00', '전출항지항구명': '인천', '위험물톤수': '-', '외내항구분': '내항', '입항수리일자': '2023-04-10', '한국인선원수': '3', '외국인선원수': '7', '예선': 'N', '도선': 'N', }, }, ]; // ─── 특이운항 / 비허가 선박 ────────────── const ALERT_VESSELS = [ { name: '제303 대양호', highlight: true }, { name: '제609 한일호', highlight: false }, { name: '한진아일랜드 고속훼리', highlight: false }, ]; // ─── AI 조업 분석 데이터 ───────────────── interface FishingAnalysis { no: number; mmsi: string; name: string; eezPermit: '허가' | '무허가'; vesselType: '어선' | '어구'; gearType: string; gearIcon: string; } const FISHING_ANALYSIS: FishingAnalysis[] = [ { no: 1, mmsi: '440162980', name: '504 FAREKIMHO', eezPermit: '무허가', vesselType: '어구', gearType: '쌍끌이', gearIcon: '🚢' }, { no: 2, mmsi: '440162980', name: '504 FAREKIMHO', eezPermit: '허가', vesselType: '어선', gearType: '범장망', gearIcon: '🚢' }, { no: 3, mmsi: '440162980', name: '504 FAREKIMHO', eezPermit: '허가', vesselType: '어선', gearType: '-', gearIcon: '' }, { no: 4, mmsi: '440162980', name: '504 FAREKIMHO', eezPermit: '허가', vesselType: '어선', gearType: '-', gearIcon: '' }, { no: 5, mmsi: '440162980', name: '504 FAREKIMHO', eezPermit: '허가', vesselType: '어선', gearType: '-', gearIcon: '' }, ]; const GEAR_FILTERS = ['외끌이', '쌍끌이', '트롤', '범장망', '형망', '채낚기', '통망']; // ─── 지도 마커 ──────────────────────── const MAP_MARKERS = [ { id: 'm1', x: 72, y: 38, label: '현재선박명', sensors: ['E', 'A', 'V'] }, { id: 'm2', x: 65, y: 43, label: '현재선박명', sensors: ['V', 'B', 'A'] }, { id: 'm3', x: 73, y: 49, label: '현재선박명', sensors: ['A', 'V', 'E'] }, ]; const VTS_MARKERS = [{ id: 'vts1', x: 52, y: 63, label: '태안연안', sub: 'VTS 신호수신 선박명' }]; const PATROL_MARKERS = [ { id: 'p1', x: 62, y: 63, label: 'E204', sub: '함정레이더 신호수신 선박명' }, { id: 'p2', x: 80, y: 70, label: 'E204', sub: '함정레이더 신호수신 선박명' }, ]; const CLUSTERS = [ { x: 58, y: 22, n: 10 }, { x: 75, y: 30, n: 5 }, { x: 52, y: 55, n: 5 }, { x: 35, y: 68, n: 5 }, ]; const RIGHT_TOOLS = [ { icon: Crosshair, label: '구역설정' }, { icon: Ruler, label: '거리' }, { icon: CircleDot, label: '면적' }, { icon: Clock, label: '거리환' }, { icon: Printer, label: '인쇄' }, { icon: Camera, label: '스냅샷' }, ]; // ─── 메인 컴포넌트 ──────────────────── export function VesselDetail() { const [expandedId, setExpandedId] = useState('2'); const [startDate, setStartDate] = useState('2023-08-20 11:30:02'); const [endDate, setEndDate] = useState('2023-08-20 11:30:02'); const [shipId, setShipId] = useState(''); const [showAiPanel, setShowAiPanel] = useState(false); const [gearChecks, setGearChecks] = useState>({ '쌍끌이': true, '범장망': true }); const mapRef = useRef(null); const buildLayers = useCallback(() => [ ...STATIC_LAYERS, // 관할해역 구역 createZoneLayer('jurisdiction', JURISDICTION_AREAS.map(a => ({ name: a.name, lat: a.lat, lng: a.lng, color: a.color, radiusM: 80000, })), 80000, 0.05), // 등심선 ...DEPTH_CONTOURS.map((contour, i) => createPolylineLayer(`depth-${i}`, contour.points as [number, number][], { color: '#06b6d4', width: 1, opacity: 0.3, dashArray: [2, 4], }) ), // 선박 마커 createMarkerLayer('vessels', MAP_MARKERS.map((m): MarkerData => { const lat = 34.2 + Math.random() * 2; const lng = 125.5 + Math.random() * 3; return { lat, lng, color: '#3b82f6', radius: 800, label: m.label }; })), // VTS 마커 createMarkerLayer('vts', VTS_MARKERS.map((m): MarkerData => ({ lat: 34.0, lng: 126.2, color: '#eab308', radius: 800, label: m.label, }))), // 함정 마커 createMarkerLayer('patrols', PATROL_MARKERS.map((m): MarkerData => ({ lat: 33.5 + Math.random(), lng: 127.0 + Math.random(), color: '#a855f7', radius: 800, label: m.label, }))), // 클러스터 createMarkerLayer('clusters', CLUSTERS.map((c, i): MarkerData => ({ lat: 33.0 + i * 0.8, lng: 125.5 + i * 0.5, color: '#6b7280', radius: 2400, label: `${c.n}척`, }))), // 선박충돌 알림 createMarkerLayer('alerts', [{ lat: 33.8, lng: 127.5, color: '#ef4444', radius: 1400, label: '선박충돌', }]), ], []); useMapLayers(mapRef, buildLayers, []); const toggleGear = (g: string) => setGearChecks((p) => ({ ...p, [g]: !p[g] })); return (
{/* ── 좌측: 항적조회 패널 ── */}
{/* 헤더: 검색 조건 */}

항적조회

시작/종료 setStartDate(e.target.value)} className="flex-1 bg-surface-overlay border border-slate-700/50 rounded px-2 py-1 text-[10px] text-label focus:outline-none focus:border-blue-500/50" /> ~ setEndDate(e.target.value)} className="flex-1 bg-surface-overlay border border-slate-700/50 rounded px-2 py-1 text-[10px] text-label focus:outline-none focus:border-blue-500/50" />
조회간격 선박ID setShipId(e.target.value)} className="flex-1 bg-surface-overlay border border-slate-700/50 rounded px-2 py-1 text-[10px] text-label focus:outline-none" />
{/* 선박 카드 */}
{VESSELS.map((v) => { const isOpen = expandedId === v.id; return (
setExpandedId(isOpen ? null : v.id)}>
ID | {v.mmsi} 호출부호 | {v.callSign} 출처 | {v.source}
{v.name} {v.type}
🇰🇷 {v.country}
{isOpen ? : }
{isOpen && (
{Object.entries(v.detail).map(([k, val], i) => (
{k} {val}
))}
)}
); })}
{/* ── 중앙: 지도 ── */}
{/* 상단 패널: 특이운항 + 비허가/재제선박 */}
{(['특이운항', '비허가/재제선박'] as const).map((title) => (
{title}
{ALERT_VESSELS.map((v, i) => ( ))}
))}
{/* AI 조업 분석 패널 (토글) */} {showAiPanel && (
{/* 헤더 */}
AI 조업 분석
{/* 선택선박 + 조업식별 필터 */}
선택선박 50
조업식별 {GEAR_FILTERS.map((g) => ( ))}
{/* 테이블 헤더 */}
구분 선박ID/선박명 EEZ허가 어선/어구 조업식별
{/* 테이블 행 */}
{FISHING_ANALYSIS.map((row) => (
{row.no}
ID | {row.mmsi}
{row.name}
{row.eezPermit} {row.vesselType}
{row.gearType !== '-' && ( {row.gearIcon && {row.gearIcon}} {row.gearType} )} {row.gearType === '-' && -}
))}
)} {/* MapLibre GL + deck.gl 지도 */} {/* 하단 좌표 바 */}
위도 34.5000 경도 126.5000 UTC 2023-07-10(월) 12:32:45 8,531 | 0 25 50NM
{/* ── 우측 도구바 ── */}
{RIGHT_TOOLS.map((t) => ( ))}
); }