import { useState, useEffect, useMemo, useCallback } from 'react';
import { Card, CardContent } from '@shared/components/ui/card';
import { Badge } from '@shared/components/ui/badge';
import {
Search, Ship, Clock, ChevronRight, ChevronLeft, Cloud,
Eye, AlertTriangle, Radio, RotateCcw,
MapPin, Brain, RefreshCw, Crosshair as CrosshairIcon, Loader2
} from 'lucide-react';
import { formatDateTime } from '@shared/utils/dateFormat';
import { GearIdentification } from './GearIdentification';
import { RealAllVessels, RealTransshipSuspects } from './RealVesselAnalysis';
import { PieChart as EcPieChart } from '@lib/charts';
import {
fetchVesselAnalysis,
filterDarkVessels,
filterTransshipSuspects,
type VesselAnalysisItem,
type VesselAnalysisStats,
} from '@/services/vesselAnalysisApi';
// ─── 중국 MMSI prefix ─────────────
const CHINA_MMSI_PREFIX = '412';
function isChinaVessel(mmsi: string): boolean {
return mmsi.startsWith(CHINA_MMSI_PREFIX);
}
// ─── 특이운항 선박 리스트 타입 ────────────────
type VesselStatus = '의심' | '양호' | '경고';
interface VesselItem {
id: string;
mmsi: string;
callSign: string;
channel: string;
source: string;
name: string;
type: string;
country: string;
status: VesselStatus;
riskPct: number;
}
function deriveVesselStatus(score: number): VesselStatus {
if (score >= 70) return '경고';
if (score >= 40) return '의심';
return '양호';
}
function mapToVesselItem(item: VesselAnalysisItem, idx: number): VesselItem {
const score = item.algorithms.riskScore.score;
return {
id: String(idx + 1),
mmsi: item.mmsi,
callSign: '-',
channel: '',
source: 'AIS',
name: item.classification.vesselType || item.mmsi,
type: item.classification.fishingPct > 0.5 ? 'Fishing' : 'Cargo',
country: 'China',
status: deriveVesselStatus(score),
riskPct: score,
};
}
// ─── VTS 연계 항목 ─────────────────────
const VTS_ITEMS = [
{ name: '경인연안', active: true },
{ name: '평택항', active: false },
{ name: '경인항', active: false },
{ name: '대산항', active: true },
{ name: '인천항', active: true },
{ name: '태안연안', active: false },
];
// ─── 환적 탐지 뷰: RealTransshipSuspects 컴포넌트 사용 ───
// ─── 서브 컴포넌트 ─────────────────────
function SemiGauge({ value, label, color }: { value: number; label: string; color: string }) {
const angle = (value / 100) * 180;
return (
{value.toFixed(2)}
%
{label}
);
}
function CircleGauge({ value, label }: { value: number; label: string }) {
const circumference = 2 * Math.PI * 42;
const offset = circumference - (value / 100) * circumference;
return (
);
}
function StatusRing({ status, riskPct }: { status: VesselStatus; riskPct: number }) {
const colors: Record = {
'의심': { ring: '#f97316', bg: 'bg-orange-500/10', text: 'text-orange-400' },
'양호': { ring: '#10b981', bg: 'bg-green-500/10', text: 'text-green-400' },
'경고': { ring: '#ef4444', bg: 'bg-red-500/10', text: 'text-red-400' },
};
const c = colors[status];
const circumference = 2 * Math.PI * 18;
const offset = circumference - (riskPct / 100) * circumference;
return (
);
}
// ─── 메인 페이지 ──────────────────────
// ─── 환적 탐지 뷰 ─────────────────────
function TransferView() {
return (
환적·접촉 탐지
선박 간 근접 접촉 및 환적 의심 행위 분석
{/* 탐지 조건 */}
탐지 조건
{/* prediction 분석 결과 기반 환적 의심 선박 */}
);
}
// ─── 메인 페이지 ──────────────────────
export function ChinaFishing() {
const [mode, setMode] = useState<'dashboard' | 'transfer' | 'gear'>('dashboard');
const [vesselTab, setVesselTab] = useState<'특이운항' | '비허가 선박' | '제재 선박' | '관심 선박'>('특이운항');
const [statsTab, setStatsTab] = useState<'불법조업 통계' | '특이선박 통계' | '위험선박 통계'>('불법조업 통계');
// API state
const [allItems, setAllItems] = useState([]);
const [apiStats, setApiStats] = useState(null);
const [serviceAvailable, setServiceAvailable] = useState(true);
const [apiLoading, setApiLoading] = useState(false);
const [apiError, setApiError] = useState('');
const loadApi = useCallback(async () => {
setApiLoading(true);
setApiError('');
try {
const res = await fetchVesselAnalysis();
setServiceAvailable(res.serviceAvailable);
setAllItems(res.items);
setApiStats(res.stats);
} catch (e: unknown) {
setApiError(e instanceof Error ? e.message : '데이터를 불러올 수 없습니다');
setServiceAvailable(false);
} finally {
setApiLoading(false);
}
}, []);
useEffect(() => { loadApi(); }, [loadApi]);
// 중국어선 필터
const chinaVessels = useMemo(
() => allItems.filter((i) => isChinaVessel(i.mmsi)),
[allItems],
);
const chinaDark = useMemo(() => filterDarkVessels(chinaVessels), [chinaVessels]);
const chinaTransship = useMemo(() => filterTransshipSuspects(chinaVessels), [chinaVessels]);
// 센서 카운터 (API 기반)
const countersRow1 = useMemo(() => [
{ label: '통합', count: allItems.length, color: '#6b7280' },
{ label: 'AIS', count: allItems.length, color: '#3b82f6' },
{ label: 'EEZ 내', count: allItems.filter((i) => i.algorithms.location.zone !== 'EEZ_OR_BEYOND').length, color: '#8b5cf6' },
{ label: '어업선', count: allItems.filter((i) => i.classification.fishingPct > 0.5).length, color: '#10b981' },
], [allItems]);
const countersRow2 = useMemo(() => [
{ label: '중국어선', count: chinaVessels.length, color: '#f97316' },
{ label: 'Dark Vessel', count: chinaDark.length, color: '#ef4444' },
{ label: '환적 의심', count: chinaTransship.length, color: '#06b6d4' },
{ label: '고위험', count: chinaVessels.filter((i) => i.algorithms.riskScore.score >= 70).length, color: '#ef4444' },
], [chinaVessels, chinaDark, chinaTransship]);
// 특이운항 선박 리스트 (중국어선 중 riskScore >= 40)
const vesselList: VesselItem[] = useMemo(
() => chinaVessels
.filter((i) => i.algorithms.riskScore.score >= 40)
.sort((a, b) => b.algorithms.riskScore.score - a.algorithms.riskScore.score)
.slice(0, 20)
.map((item, idx) => mapToVesselItem(item, idx)),
[chinaVessels],
);
// 위험도별 분포 (도넛 차트용)
const riskDistribution = useMemo(() => {
const critical = chinaVessels.filter((i) => i.algorithms.riskScore.level === 'CRITICAL').length;
const high = chinaVessels.filter((i) => i.algorithms.riskScore.level === 'HIGH').length;
const medium = chinaVessels.filter((i) => i.algorithms.riskScore.level === 'MEDIUM').length;
const low = chinaVessels.filter((i) => i.algorithms.riskScore.level === 'LOW').length;
return { critical, high, medium, low, total: chinaVessels.length };
}, [chinaVessels]);
// 안전도 지수 계산
const safetyIndex = useMemo(() => {
if (chinaVessels.length === 0) return { risk: 0, safety: 100 };
const avgRisk = chinaVessels.reduce((s, i) => s + i.algorithms.riskScore.score, 0) / chinaVessels.length;
return { risk: Number((avgRisk / 10).toFixed(2)), safety: Number(((100 - avgRisk) / 10).toFixed(2)) };
}, [chinaVessels]);
const vesselTabs = ['특이운항', '비허가 선박', '제재 선박', '관심 선박'] as const;
const statsTabs = ['불법조업 통계', '특이선박 통계', '위험선박 통계'] as const;
const modeTabs = [
{ key: 'dashboard' as const, icon: Brain, label: 'AI 감시 대시보드' },
{ key: 'transfer' as const, icon: RefreshCw, label: '환적·접촉 탐지' },
{ key: 'gear' as const, icon: CrosshairIcon, label: '어구/어망 판별' },
];
return (
{/* ── 모드 탭 (AI 대시보드 / 환적 탐지) ── */}
{modeTabs.map((tab) => (
))}
{/* 환적 탐지 모드 */}
{mode === 'transfer' &&
}
{/* 어구/어망 판별 모드 */}
{mode === 'gear' &&
}
{/* AI 대시보드 모드 */}
{mode === 'dashboard' && <>
{!serviceAvailable && (
iran 분석 서비스 미연결 - 실시간 데이터를 불러올 수 없습니다
)}
{apiError &&
에러: {apiError}
}
{apiLoading && (
)}
{/* iran 백엔드 실시간 분석 결과 */}
{/* ── 상단 바: 기준일 + 검색 ── */}
{/* ── 상단 영역: 통항량 + 안전도 분석 + 관심영역 ── */}
{/* 해역별 통항량 */}
해역별 통항량
{apiStats && (
분석 대상
{apiStats.total.toLocaleString()}척
)}
해역 전체 통항량
{allItems.length.toLocaleString()}
(척)
{/* 카운터 Row 1 */}
{countersRow1.map((c) => (
{c.label}
{c.count.toLocaleString()}
))}
{/* 카운터 Row 2 */}
{countersRow2.map((c) => (
{c.label}
0 ? 'text-heading' : 'text-muted'}`}>
{c.count > 0 ? c.count.toLocaleString() : '-'}
))}
{/* 안전도 분석 */}
{/* 관심영역 안전도 */}
관심영역 안전도
설정한 관심 영역을 선택후 조회를 눌러주세요.
0 ? Number(((1 - riskDistribution.critical / Math.max(chinaVessels.length, 1)) * 100).toFixed(1)) : 100} label="" />
{/* ── 하단 영역: 선박 리스트 + 통계 ── */}
{/* 좌: 선박 리스트 (탭) */}
{/* 탭 헤더 */}
{vesselTabs.map((tab) => (
))}
{/* 선박 목록 */}
{vesselList.length === 0 && (
{apiLoading ? '데이터 로딩 중...' : '중국어선 특이운항 데이터가 없습니다'}
)}
{vesselList.map((v) => (
MMSI | {v.mmsi}
출처 | {v.source}
{v.name}
{v.type}
{v.country}
))}
{/* 우: 통계 + 하단 카드 3개 */}
{/* 통계 차트 */}
{/* 탭 */}
{statsTabs.map((tab) => (
))}
{/* 월별 통계 - API 미지원, 준비중 안내 */}
월별 불법조업 통계
월별 집계 API 연동 준비중입니다. 실시간 위험도 분포는 우측 도넛을 참고하세요.
{/* 위험도 분포 도넛 */}
{riskDistribution.total}
중국어선
CRITICAL {riskDistribution.critical}
HIGH {riskDistribution.high}
MEDIUM {riskDistribution.medium}
LOW {riskDistribution.low}
{/* 다운로드 버튼 */}
{/* 하단 카드 3개 */}
{/* 최근 위성영상 분석 */}
최근 위성영상 분석
VIIRS
| 2023-08-11 02:00:00
이미지
| BSG-117-20230194-orho.tif
CSV
| 2023.07.17_ship_dection.clustog.csv
{/* 기상 예보 */}
기상 예보
전남서부남해앞바다
28.1
°C
흐림 남~남서 🌊 0.5~0.5m
{/* VTS연계 현황 */}
VTS연계 현황
{VTS_ITEMS.map((vts) => (
{vts.name}
))}
>}
);
}