fix: 선박 분류 오류 수정 + 배지 색상 통일 #63
@ -2,6 +2,7 @@ import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { GeoEvent, Ship } from '../../types';
|
||||
import type { OsintItem } from '../../services/osint';
|
||||
import { getMarineTrafficCategory } from '../../utils/marineTraffic';
|
||||
|
||||
type DashboardTab = 'iran' | 'korea';
|
||||
|
||||
@ -268,36 +269,20 @@ const TYPE_COLORS: Record<GeoEvent['type'], string> = {
|
||||
};
|
||||
|
||||
// MarineTraffic-style ship type classification
|
||||
function getShipMTCategory(typecode?: string, category?: string): string {
|
||||
if (!typecode) {
|
||||
if (category === 'tanker') return 'tanker';
|
||||
if (category === 'cargo') return 'cargo';
|
||||
if (category === 'destroyer' || category === 'warship' || category === 'carrier' || category === 'patrol') return 'military';
|
||||
return 'unspecified';
|
||||
}
|
||||
const code = typecode.toUpperCase();
|
||||
if (code === 'VLCC' || code === 'LNG' || code === 'LPG') return 'tanker';
|
||||
if (code === 'CONT' || code === 'BULK') return 'cargo';
|
||||
if (code === 'DDH' || code === 'DDG' || code === 'CVN' || code === 'FFG' || code === 'LCS' || code === 'MCM' || code === 'PC') return 'military';
|
||||
if (code.startsWith('A1')) return 'tanker';
|
||||
if (code.startsWith('A2') || code.startsWith('A3')) return 'cargo';
|
||||
if (code.startsWith('B')) return 'passenger';
|
||||
if (code.startsWith('C')) return 'fishing';
|
||||
if (code.startsWith('D') || code.startsWith('E')) return 'tug_special';
|
||||
return 'unspecified';
|
||||
}
|
||||
// getMarineTrafficCategory → getMarineTrafficCategory (utils/marineTraffic.ts)로 통합
|
||||
|
||||
// MarineTraffic-style category colors (labels come from i18n)
|
||||
const MT_CATEGORY_COLORS: Record<string, string> = {
|
||||
cargo: '#8bc34a',
|
||||
tanker: '#e91e63',
|
||||
passenger: '#2196f3',
|
||||
high_speed: '#ff9800',
|
||||
tug_special: '#00bcd4',
|
||||
fishing: '#ff5722',
|
||||
pleasure: '#9c27b0',
|
||||
military: '#607d8b',
|
||||
unspecified: '#9e9e9e',
|
||||
cargo: 'var(--kcg-ship-cargo)',
|
||||
tanker: 'var(--kcg-ship-tanker)',
|
||||
passenger: 'var(--kcg-ship-passenger)',
|
||||
high_speed: 'var(--kcg-ship-highspeed)',
|
||||
tug_special: 'var(--kcg-ship-tug)',
|
||||
fishing: 'var(--kcg-ship-fishing)',
|
||||
pleasure: 'var(--kcg-ship-pleasure)',
|
||||
military: 'var(--kcg-ship-military)',
|
||||
other: 'var(--kcg-ship-other)',
|
||||
unspecified: 'var(--kcg-ship-unknown)',
|
||||
};
|
||||
|
||||
const NEWS_CATEGORY_ICONS: Record<BreakingNews['category'], string> = {
|
||||
@ -443,7 +428,7 @@ export function EventLog({ events, currentTime, totalShipCount: _totalShipCount,
|
||||
</div>
|
||||
<div className="iran-mil-list">
|
||||
{koreanShips.slice(0, 30).map(s => {
|
||||
const cat = getShipMTCategory(s.typecode, s.category);
|
||||
const cat = getMarineTrafficCategory(s.typecode, s.category);
|
||||
const mtColor = MT_CATEGORY_COLORS[cat] || '#888';
|
||||
const mtLabel = t(`ships:mtType.${cat}`, { defaultValue: t('ships:mtType.unspecified') });
|
||||
return (
|
||||
@ -612,7 +597,7 @@ export function EventLog({ events, currentTime, totalShipCount: _totalShipCount,
|
||||
{koreanShips.length > 0 && (() => {
|
||||
const groups: Record<string, Ship[]> = {};
|
||||
for (const s of koreanShips) {
|
||||
const cat = getShipMTCategory(s.typecode, s.category);
|
||||
const cat = getMarineTrafficCategory(s.typecode, s.category);
|
||||
if (!groups[cat]) groups[cat] = [];
|
||||
groups[cat].push(s);
|
||||
}
|
||||
@ -659,7 +644,7 @@ export function EventLog({ events, currentTime, totalShipCount: _totalShipCount,
|
||||
{chineseShips.length > 0 && (() => {
|
||||
const groups: Record<string, Ship[]> = {};
|
||||
for (const s of chineseShips) {
|
||||
const cat = getShipMTCategory(s.typecode, s.category);
|
||||
const cat = getMarineTrafficCategory(s.typecode, s.category);
|
||||
if (!groups[cat]) groups[cat] = [];
|
||||
groups[cat].push(s);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// MarineTraffic-style ship classification
|
||||
// Maps S&P STAT5CODE prefixes and our custom typecodes to MT categories
|
||||
// Maps S&P STAT5CODE prefixes, VesselType strings, and custom typecodes to MT categories
|
||||
export function getMarineTrafficCategory(typecode?: string, category?: string): string {
|
||||
if (!typecode) {
|
||||
// Fallback to our internal category
|
||||
@ -10,30 +10,14 @@ export function getMarineTrafficCategory(typecode?: string, category?: string):
|
||||
}
|
||||
const code = typecode.toUpperCase();
|
||||
|
||||
// Our custom typecodes
|
||||
// Our custom typecodes (exact match)
|
||||
if (code === 'VLCC' || code === 'LNG' || code === 'LPG') return 'tanker';
|
||||
if (code === 'CONT' || code === 'BULK') return 'cargo';
|
||||
if (code === 'DDH' || code === 'DDG' || code === 'CVN' || code === 'FFG' || code === 'LCS' || code === 'MCM' || code === 'PC') return 'military';
|
||||
if (code === 'DDH' || code === 'DDG' || code === 'CVN' || code === 'FFG' || code === 'LCS' || code === 'MCM' || code === 'PC' || code === 'LPH') return 'military';
|
||||
if (code === 'PASS') return 'passenger';
|
||||
|
||||
// S&P STAT5CODE (IHS StatCode5) — first 2 chars determine main category
|
||||
// A1x = Tankers (crude, products, chemical, LPG, LNG)
|
||||
if (code.startsWith('A1')) return 'tanker';
|
||||
// A2x = Bulk carriers
|
||||
if (code.startsWith('A2')) return 'cargo';
|
||||
// A3x = General cargo / Container / Reefer / Ro-Ro
|
||||
if (code.startsWith('A3')) return 'cargo';
|
||||
// B1x / B2x = Passenger / Cruise / Ferry
|
||||
if (code.startsWith('B')) return 'passenger';
|
||||
// C1x = Fishing
|
||||
if (code.startsWith('C')) return 'fishing';
|
||||
// D1x = Offshore (tugs, supply, etc.)
|
||||
if (code.startsWith('D')) return 'tug_special';
|
||||
// E = Other activities (research, cable layers, dredgers)
|
||||
if (code.startsWith('E')) return 'tug_special';
|
||||
// X = Non-propelled (barges)
|
||||
if (code.startsWith('X')) return 'unspecified';
|
||||
|
||||
// S&P VesselType strings
|
||||
// VesselType strings (e.g. "Cargo", "Tanker", "Passenger") — match BEFORE STAT5CODE
|
||||
// to avoid "Cargo" matching STAT5CODE prefix "C" → fishing
|
||||
const lower = code.toLowerCase();
|
||||
if (lower.includes('tanker')) return 'tanker';
|
||||
if (lower.includes('cargo') || lower.includes('container') || lower.includes('bulk')) return 'cargo';
|
||||
@ -42,6 +26,20 @@ export function getMarineTrafficCategory(typecode?: string, category?: string):
|
||||
if (lower.includes('tug') || lower.includes('supply') || lower.includes('offshore')) return 'tug_special';
|
||||
if (lower.includes('high speed')) return 'high_speed';
|
||||
if (lower.includes('pleasure') || lower.includes('yacht') || lower.includes('sailing')) return 'pleasure';
|
||||
if (lower.includes('pilot') || lower.includes('search') || lower.includes('law enforcement')) return 'tug_special';
|
||||
if (lower.includes('naval') || lower.includes('military')) return 'military';
|
||||
|
||||
// S&P STAT5CODE (IHS StatCode5) — 2nd char is digit (e.g. "A1xxxx", "B2xxxx")
|
||||
if (code.length >= 2 && /\d/.test(code[1])) {
|
||||
if (code.startsWith('A1')) return 'tanker';
|
||||
if (code.startsWith('A2')) return 'cargo';
|
||||
if (code.startsWith('A3')) return 'cargo';
|
||||
if (code.startsWith('B')) return 'passenger';
|
||||
if (code.startsWith('C')) return 'fishing';
|
||||
if (code.startsWith('D')) return 'tug_special';
|
||||
if (code.startsWith('E')) return 'tug_special';
|
||||
if (code.startsWith('X')) return 'unspecified';
|
||||
}
|
||||
|
||||
return 'unspecified';
|
||||
}
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user