kcg-monitoring/src/components/NavWarningLayer.tsx
htlee ccdfb3517b feat: KCG 모니터링 대시보드 초기 프로젝트 구성
React 19 + TypeScript + Vite + MapLibre 기반 해양 모니터링 대시보드.
선박 AIS, 항공기, CCTV, 위성, 해양 인프라 등 다중 레이어 지원.
ESLint React Compiler 규칙 조정 및 lint 에러 수정 포함.
2026-03-17 09:01:18 +09:00

124 lines
4.8 KiB
TypeScript

import { useState } from 'react';
import { Marker, Popup } from 'react-map-gl/maplibre';
import { NAV_WARNINGS, NW_LEVEL_LABEL, NW_ORG_LABEL } from '../services/navWarning';
import type { NavWarning, NavWarningLevel, TrainingOrg } from '../services/navWarning';
const LEVEL_COLOR: Record<NavWarningLevel, string> = {
danger: '#ef4444',
caution: '#eab308',
info: '#3b82f6',
};
const ORG_COLOR: Record<TrainingOrg, string> = {
'해군': '#8b5cf6',
'해병대': '#22c55e',
'공군': '#f97316',
'육군': '#ef4444',
'해경': '#3b82f6',
'국과연': '#eab308',
};
function WarningIcon({ level, org, size }: { level: NavWarningLevel; org: TrainingOrg; size: number }) {
const color = ORG_COLOR[org];
if (level === 'danger') {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<path d="M12 3 L22 20 L2 20 Z" fill="rgba(0,0,0,0.5)" stroke={color} strokeWidth="1.5" />
<line x1="12" y1="9" x2="12" y2="14" stroke={color} strokeWidth="2" strokeLinecap="round" />
<circle cx="12" cy="17" r="1" fill={color} />
</svg>
);
}
// caution (해경 등)
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<path d="M12 2 L22 12 L12 22 L2 12 Z" fill="rgba(0,0,0,0.5)" stroke={color} strokeWidth="1.2" />
<line x1="12" y1="8" x2="12" y2="13" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<circle cx="12" cy="16" r="1" fill={color} />
</svg>
);
}
export function NavWarningLayer() {
const [selected, setSelected] = useState<NavWarning | null>(null);
return (
<>
{NAV_WARNINGS.map(w => {
const color = ORG_COLOR[w.org];
const size = w.level === 'danger' ? 16 : 14;
return (
<Marker key={w.id} longitude={w.lng} latitude={w.lat} anchor="center"
onClick={(e) => { e.originalEvent.stopPropagation(); setSelected(w); }}>
<div style={{
cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center',
filter: `drop-shadow(0 0 4px ${color}88)`,
}}>
<WarningIcon level={w.level} org={w.org} size={size} />
<div style={{
fontSize: 5, color, marginTop: 0,
textShadow: `0 0 3px ${color}, 0 0 2px #000`,
whiteSpace: 'nowrap', fontWeight: 700, letterSpacing: 0.3,
}}>
{w.id}
</div>
</div>
</Marker>
);
})}
{selected && (
<Popup longitude={selected.lng} latitude={selected.lat}
onClose={() => setSelected(null)} closeOnClick={false}
anchor="bottom" maxWidth="320px" className="gl-popup">
<div style={{ fontFamily: 'monospace', fontSize: 12, minWidth: 240 }}>
<div style={{
background: ORG_COLOR[selected.org], color: '#fff',
padding: '4px 8px', borderRadius: '4px 4px 0 0',
margin: '-10px -10px 8px -10px',
fontWeight: 700, fontSize: 12,
display: 'flex', alignItems: 'center', gap: 6,
}}>
{selected.title}
</div>
<div style={{ display: 'flex', gap: 4, marginBottom: 6, flexWrap: 'wrap' }}>
<span style={{
background: LEVEL_COLOR[selected.level], color: '#fff',
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 700,
}}>{NW_LEVEL_LABEL[selected.level]}</span>
<span style={{
background: ORG_COLOR[selected.org] + '33', color: ORG_COLOR[selected.org],
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 700,
border: `1px solid ${ORG_COLOR[selected.org]}44`,
}}>{NW_ORG_LABEL[selected.org]}</span>
<span style={{
background: '#1a1a2e', color: '#888',
padding: '1px 6px', borderRadius: 3, fontSize: 10,
}}>{selected.area}</span>
</div>
<div style={{ fontSize: 11, color: '#ccc', marginBottom: 6, lineHeight: 1.4 }}>
{selected.description}
</div>
<div style={{ fontSize: 9, color: '#666', display: 'flex', flexDirection: 'column', gap: 2 }}>
<div>: {selected.altitude}</div>
<div>{selected.lat.toFixed(4)}°N, {selected.lng.toFixed(4)}°E</div>
<div>: {selected.source}</div>
</div>
<a
href="https://www.khoa.go.kr/nwb/mainPage.do?lang=ko"
target="_blank"
rel="noopener noreferrer"
style={{
display: 'block', marginTop: 6,
fontSize: 10, color: '#3b82f6', textDecoration: 'underline',
}}
>KHOA </a>
</div>
</Popup>
)}
</>
);
}