refactor: Phase 3 — StaticFacilityPopup 추출 (KoreaMap 935줄→742줄)
- StaticFacilityPopup: 시설 클릭 Popup 로직 독립 컴포넌트 (207줄) - KoreaMap: IIFE Popup 블록(~200줄) → 컴포넌트 호출 3줄로 대체 - SUB_META/KIND_DEFAULT 상수 + 배지/필드 렌더링 모두 이동
This commit is contained in:
부모
aff17588b2
커밋
728936439b
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Map, NavigationControl, Marker, Popup, Source, Layer } from 'react-map-gl/maplibre';
|
||||
import { Map, NavigationControl, Marker, Source, Layer } from 'react-map-gl/maplibre';
|
||||
import type { MapRef } from 'react-map-gl/maplibre';
|
||||
import { ScatterplotLayer, TextLayer } from '@deck.gl/layers';
|
||||
import { DeckGLOverlay } from '../layers/DeckGLOverlay';
|
||||
@ -20,6 +20,7 @@ import { EezLayer } from './EezLayer';
|
||||
// PiracyLayer, WindFarmLayer, PortLayer, MilitaryBaseLayer, GovBuildingLayer,
|
||||
// NKLaunchLayer, NKMissileEventLayer → useStaticDeckLayers로 전환됨
|
||||
import { ChineseFishingOverlay } from './ChineseFishingOverlay';
|
||||
import { StaticFacilityPopup } from './StaticFacilityPopup';
|
||||
// HazardFacilityLayer, CnFacilityLayer, JpFacilityLayer → useStaticDeckLayers로 전환됨
|
||||
import { AnalysisOverlay } from './AnalysisOverlay';
|
||||
import { FleetClusterLayer } from './FleetClusterLayer';
|
||||
@ -641,203 +642,9 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
||||
...analysisDeckLayers,
|
||||
].filter(Boolean)} />
|
||||
{/* 정적 마커 클릭 Popup — 통합 리치 디자인 */}
|
||||
{staticPickInfo && (() => {
|
||||
const obj = staticPickInfo.object;
|
||||
const kind = staticPickInfo.kind;
|
||||
const lat = obj.lat ?? obj.launchLat ?? 0;
|
||||
const lng = obj.lng ?? obj.launchLng ?? 0;
|
||||
if (!lat || !lng) return null;
|
||||
|
||||
// ── kind + subType 기반 메타 결정 ──
|
||||
const SUB_META: Record<string, Record<string, { icon: string; color: string; label: string }>> = {
|
||||
hazard: {
|
||||
petrochemical: { icon: '🏭', color: '#f97316', label: '석유화학단지' },
|
||||
lng: { icon: '🔵', color: '#06b6d4', label: 'LNG저장기지' },
|
||||
oilTank: { icon: '🛢️', color: '#eab308', label: '유류저장탱크' },
|
||||
hazardPort: { icon: '⚠️', color: '#ef4444', label: '위험물항만하역' },
|
||||
nuclear: { icon: '☢️', color: '#a855f7', label: '원자력발전소' },
|
||||
thermal: { icon: '🔥', color: '#64748b', label: '화력발전소' },
|
||||
shipyard: { icon: '🚢', color: '#0ea5e9', label: '조선소' },
|
||||
wastewater: { icon: '💧', color: '#10b981', label: '폐수처리장' },
|
||||
heavyIndustry: { icon: '⚙️', color: '#94a3b8', label: '시멘트/제철소' },
|
||||
},
|
||||
overseas: {
|
||||
nuclear: { icon: '☢️', color: '#ef4444', label: '핵발전소' },
|
||||
thermal: { icon: '🔥', color: '#f97316', label: '화력발전소' },
|
||||
naval: { icon: '⚓', color: '#3b82f6', label: '해군기지' },
|
||||
airbase: { icon: '✈️', color: '#22d3ee', label: '공군기지' },
|
||||
army: { icon: '🪖', color: '#22c55e', label: '육군기지' },
|
||||
shipyard:{ icon: '🚢', color: '#94a3b8', label: '조선소' },
|
||||
},
|
||||
militaryBase: {
|
||||
naval: { icon: '⚓', color: '#3b82f6', label: '해군기지' },
|
||||
airforce:{ icon: '✈️', color: '#22d3ee', label: '공군기지' },
|
||||
army: { icon: '🪖', color: '#22c55e', label: '육군기지' },
|
||||
missile: { icon: '🚀', color: '#ef4444', label: '미사일기지' },
|
||||
joint: { icon: '⭐', color: '#f59e0b', label: '합동사령부' },
|
||||
},
|
||||
govBuilding: {
|
||||
executive: { icon: '🏛️', color: '#f59e0b', label: '행정부' },
|
||||
legislature: { icon: '🏛️', color: '#3b82f6', label: '입법부' },
|
||||
military_hq: { icon: '⭐', color: '#ef4444', label: '군사령부' },
|
||||
intelligence: { icon: '🔍', color: '#8b5cf6', label: '정보기관' },
|
||||
foreign: { icon: '🌐', color: '#06b6d4', label: '외교부' },
|
||||
maritime: { icon: '⚓', color: '#0ea5e9', label: '해양기관' },
|
||||
defense: { icon: '🛡️', color: '#dc2626', label: '국방부' },
|
||||
},
|
||||
nkLaunch: {
|
||||
icbm: { icon: '🚀', color: '#dc2626', label: 'ICBM 발사장' },
|
||||
irbm: { icon: '🚀', color: '#ef4444', label: 'IRBM/MRBM' },
|
||||
srbm: { icon: '🎯', color: '#f97316', label: '단거리탄도미사일' },
|
||||
slbm: { icon: '🔱', color: '#3b82f6', label: 'SLBM 발사' },
|
||||
cruise: { icon: '✈️', color: '#8b5cf6', label: '순항미사일' },
|
||||
artillery: { icon: '💥', color: '#eab308', label: '해안포/장사정포' },
|
||||
mlrs: { icon: '💥', color: '#f59e0b', label: '방사포(MLRS)' },
|
||||
},
|
||||
coastGuard: {
|
||||
hq: { icon: '🏢', color: '#3b82f6', label: '본청' },
|
||||
regional: { icon: '🏢', color: '#60a5fa', label: '지방청' },
|
||||
station: { icon: '🚔', color: '#22d3ee', label: '해양경찰서' },
|
||||
substation: { icon: '🏠', color: '#94a3b8', label: '파출소' },
|
||||
vts: { icon: '📡', color: '#f59e0b', label: 'VTS센터' },
|
||||
navy: { icon: '⚓', color: '#3b82f6', label: '해군부대' },
|
||||
},
|
||||
airport: {
|
||||
international: { icon: '✈️', color: '#a78bfa', label: '국제공항' },
|
||||
domestic: { icon: '🛩️', color: '#60a5fa', label: '국내공항' },
|
||||
military: { icon: '✈️', color: '#ef4444', label: '군용비행장' },
|
||||
},
|
||||
navWarning: {
|
||||
danger: { icon: '⚠️', color: '#ef4444', label: '위험' },
|
||||
caution: { icon: '⚠️', color: '#eab308', label: '주의' },
|
||||
info: { icon: 'ℹ️', color: '#3b82f6', label: '정보' },
|
||||
},
|
||||
piracy: {
|
||||
critical: { icon: '☠️', color: '#ef4444', label: '극고위험' },
|
||||
high: { icon: '☠️', color: '#f97316', label: '고위험' },
|
||||
moderate: { icon: '☠️', color: '#eab308', label: '주의' },
|
||||
},
|
||||
};
|
||||
|
||||
const KIND_DEFAULT: Record<string, { icon: string; color: string; label: string }> = {
|
||||
port: { icon: '⚓', color: '#3b82f6', label: '항구' },
|
||||
windFarm: { icon: '🌀', color: '#00bcd4', label: '풍력단지' },
|
||||
militaryBase: { icon: '🪖', color: '#ef4444', label: '군사시설' },
|
||||
govBuilding: { icon: '🏛️', color: '#f59e0b', label: '정부기관' },
|
||||
nkLaunch: { icon: '🚀', color: '#dc2626', label: '북한 발사장' },
|
||||
nkMissile: { icon: '💣', color: '#ef4444', label: '미사일 낙하' },
|
||||
coastGuard: { icon: '🚔', color: '#4dabf7', label: '해양경찰' },
|
||||
airport: { icon: '✈️', color: '#a78bfa', label: '공항' },
|
||||
navWarning: { icon: '⚠️', color: '#eab308', label: '항행경보' },
|
||||
piracy: { icon: '☠️', color: '#ef4444', label: '해적위험' },
|
||||
infra: { icon: '⚡', color: '#ffeb3b', label: '발전/변전' },
|
||||
hazard: { icon: '⚠️', color: '#ef4444', label: '위험시설' },
|
||||
cnFacility: { icon: '📍', color: '#ef4444', label: '중국시설' },
|
||||
jpFacility: { icon: '📍', color: '#f472b6', label: '일본시설' },
|
||||
};
|
||||
|
||||
// subType 키 결정
|
||||
const subKey = obj.type ?? obj.subType ?? obj.level ?? '';
|
||||
const subGroup = kind === 'cnFacility' || kind === 'jpFacility' ? 'overseas' : kind;
|
||||
const meta = SUB_META[subGroup]?.[subKey] ?? KIND_DEFAULT[kind] ?? { icon: '📍', color: '#64748b', label: kind };
|
||||
|
||||
// 국가 플래그
|
||||
const COUNTRY_FLAG: Record<string, string> = { CN: '🇨🇳', JP: '🇯🇵', KP: '🇰🇵', KR: '🇰🇷', TW: '🇹🇼' };
|
||||
const flag = kind === 'cnFacility' ? '🇨🇳' : kind === 'jpFacility' ? '🇯🇵' : COUNTRY_FLAG[obj.country] ?? '';
|
||||
const countryName = kind === 'cnFacility' ? '중국' : kind === 'jpFacility' ? '일본'
|
||||
: { CN: '중국', JP: '일본', KP: '북한', KR: '한국', TW: '대만' }[obj.country] ?? '';
|
||||
|
||||
// 이름 결정
|
||||
const title = obj.nameKo || obj.name || obj.launchNameKo || obj.title || kind;
|
||||
|
||||
return (
|
||||
<Popup longitude={lng} latitude={lat} anchor="bottom"
|
||||
onClose={() => setStaticPickInfo(null)} closeOnClick={false}
|
||||
maxWidth="280px" className="gl-popup"
|
||||
>
|
||||
<div className="popup-body-sm" style={{ minWidth: 200 }}>
|
||||
{/* 컬러 헤더 */}
|
||||
<div className="popup-header" style={{ background: meta.color, color: '#000', gap: 6, padding: '4px 8px' }}>
|
||||
<span>{meta.icon}</span> {title}
|
||||
</div>
|
||||
{/* 배지 행 */}
|
||||
<div style={{ display: 'flex', gap: 4, marginBottom: 6, flexWrap: 'wrap' }}>
|
||||
<span style={{
|
||||
background: meta.color, color: '#000',
|
||||
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 700,
|
||||
}}>
|
||||
{meta.label}
|
||||
</span>
|
||||
{flag && (
|
||||
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>
|
||||
{flag} {countryName}
|
||||
</span>
|
||||
)}
|
||||
{kind === 'hazard' && (
|
||||
<span style={{
|
||||
background: 'rgba(239,68,68,0.15)', color: '#ef4444',
|
||||
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 600,
|
||||
border: '1px solid rgba(239,68,68,0.3)',
|
||||
}}>⚠️ 위험시설</span>
|
||||
)}
|
||||
{kind === 'port' && (
|
||||
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>
|
||||
{obj.type === 'major' ? '주요항' : '중소항'}
|
||||
</span>
|
||||
)}
|
||||
{kind === 'airport' && obj.intl && (
|
||||
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>국제선</span>
|
||||
)}
|
||||
</div>
|
||||
{/* 설명 */}
|
||||
{obj.description && (
|
||||
<div style={{ fontSize: 10, color: '#999', marginBottom: 4, lineHeight: 1.5 }}>{obj.description}</div>
|
||||
)}
|
||||
{obj.detail && (
|
||||
<div style={{ fontSize: 10, color: '#888', marginBottom: 4, lineHeight: 1.4 }}>{obj.detail}</div>
|
||||
)}
|
||||
{obj.note && (
|
||||
<div style={{ fontSize: 10, color: '#888', marginBottom: 4, lineHeight: 1.4 }}>{obj.note}</div>
|
||||
)}
|
||||
{/* 필드 그리드 */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{obj.operator && <div><span className="popup-label">운영: </span>{obj.operator}</div>}
|
||||
{obj.capacity && <div><span className="popup-label">규모: </span><strong>{obj.capacity}</strong></div>}
|
||||
{obj.output && <div><span className="popup-label">출력: </span><strong>{obj.output}</strong></div>}
|
||||
{obj.source && <div><span className="popup-label">연료: </span>{obj.source}</div>}
|
||||
{obj.capacityMW && <div><span className="popup-label">용량: </span><strong>{obj.capacityMW}MW</strong></div>}
|
||||
{obj.turbines && <div><span className="popup-label">터빈: </span>{obj.turbines}기</div>}
|
||||
{obj.status && <div><span className="popup-label">상태: </span>{obj.status}</div>}
|
||||
{obj.year && <div><span className="popup-label">연도: </span>{obj.year}년</div>}
|
||||
{obj.region && <div><span className="popup-label">지역: </span>{obj.region}</div>}
|
||||
{obj.org && <div><span className="popup-label">기관: </span>{obj.org}</div>}
|
||||
{obj.area && <div><span className="popup-label">해역: </span>{obj.area}</div>}
|
||||
{obj.altitude && <div><span className="popup-label">고도: </span>{obj.altitude}</div>}
|
||||
{obj.address && <div><span className="popup-label">주소: </span>{obj.address}</div>}
|
||||
{obj.recentUse && <div><span className="popup-label">최근 사용: </span>{obj.recentUse}</div>}
|
||||
{obj.recentIncidents != null && <div><span className="popup-label">최근 1년: </span><strong>{obj.recentIncidents}건</strong></div>}
|
||||
{obj.icao && <div><span className="popup-label">ICAO: </span>{obj.icao}</div>}
|
||||
{kind === 'nkMissile' && (
|
||||
<>
|
||||
{obj.typeKo && <div><span className="popup-label">미사일: </span>{obj.typeKo}</div>}
|
||||
{obj.date && <div><span className="popup-label">발사일: </span>{obj.date} {obj.time}</div>}
|
||||
{obj.distanceKm && <div><span className="popup-label">사거리: </span>{obj.distanceKm}km</div>}
|
||||
{obj.altitudeKm && <div><span className="popup-label">최고고도: </span>{obj.altitudeKm}km</div>}
|
||||
{obj.flightMin && <div><span className="popup-label">비행시간: </span>{obj.flightMin}분</div>}
|
||||
{obj.launchNameKo && <div><span className="popup-label">발사지: </span>{obj.launchNameKo}</div>}
|
||||
</>
|
||||
)}
|
||||
{obj.name && obj.nameKo && obj.name !== obj.nameKo && (
|
||||
<div><span className="popup-label">영문: </span>{obj.name}</div>
|
||||
)}
|
||||
<div style={{ fontSize: 9, color: '#666', marginTop: 2 }}>
|
||||
{lat.toFixed(4)}°N, {lng.toFixed(4)}°E
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
);
|
||||
})()}
|
||||
{staticPickInfo && (
|
||||
<StaticFacilityPopup pickInfo={staticPickInfo} onClose={() => setStaticPickInfo(null)} />
|
||||
)}
|
||||
{layers.osint && <OsintMapLayer osintFeed={osintFeed} currentTime={currentTime} />}
|
||||
{layers.eez && <EezLayer />}
|
||||
|
||||
|
||||
207
frontend/src/components/korea/StaticFacilityPopup.tsx
Normal file
207
frontend/src/components/korea/StaticFacilityPopup.tsx
Normal file
@ -0,0 +1,207 @@
|
||||
import { Popup } from 'react-map-gl/maplibre';
|
||||
import type { StaticPickInfo } from '../../hooks/layers/types';
|
||||
|
||||
interface StaticFacilityPopupProps {
|
||||
pickInfo: StaticPickInfo;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const StaticFacilityPopup = ({ pickInfo, onClose }: StaticFacilityPopupProps) => {
|
||||
const obj = pickInfo.object as any; // eslint-disable-line @typescript-eslint/no-explicit-any -- StaticPickedObject union requires loose access
|
||||
const kind = pickInfo.kind;
|
||||
const lat = obj.lat ?? obj.launchLat ?? 0;
|
||||
const lng = obj.lng ?? obj.launchLng ?? 0;
|
||||
if (!lat || !lng) return null;
|
||||
|
||||
// ── kind + subType 기반 메타 결정 ──
|
||||
const SUB_META: Record<string, Record<string, { icon: string; color: string; label: string }>> = {
|
||||
hazard: {
|
||||
petrochemical: { icon: '🏭', color: '#f97316', label: '석유화학단지' },
|
||||
lng: { icon: '🔵', color: '#06b6d4', label: 'LNG저장기지' },
|
||||
oilTank: { icon: '🛢️', color: '#eab308', label: '유류저장탱크' },
|
||||
hazardPort: { icon: '⚠️', color: '#ef4444', label: '위험물항만하역' },
|
||||
nuclear: { icon: '☢️', color: '#a855f7', label: '원자력발전소' },
|
||||
thermal: { icon: '🔥', color: '#64748b', label: '화력발전소' },
|
||||
shipyard: { icon: '🚢', color: '#0ea5e9', label: '조선소' },
|
||||
wastewater: { icon: '💧', color: '#10b981', label: '폐수처리장' },
|
||||
heavyIndustry: { icon: '⚙️', color: '#94a3b8', label: '시멘트/제철소' },
|
||||
},
|
||||
overseas: {
|
||||
nuclear: { icon: '☢️', color: '#ef4444', label: '핵발전소' },
|
||||
thermal: { icon: '🔥', color: '#f97316', label: '화력발전소' },
|
||||
naval: { icon: '⚓', color: '#3b82f6', label: '해군기지' },
|
||||
airbase: { icon: '✈️', color: '#22d3ee', label: '공군기지' },
|
||||
army: { icon: '🪖', color: '#22c55e', label: '육군기지' },
|
||||
shipyard:{ icon: '🚢', color: '#94a3b8', label: '조선소' },
|
||||
},
|
||||
militaryBase: {
|
||||
naval: { icon: '⚓', color: '#3b82f6', label: '해군기지' },
|
||||
airforce:{ icon: '✈️', color: '#22d3ee', label: '공군기지' },
|
||||
army: { icon: '🪖', color: '#22c55e', label: '육군기지' },
|
||||
missile: { icon: '🚀', color: '#ef4444', label: '미사일기지' },
|
||||
joint: { icon: '⭐', color: '#f59e0b', label: '합동사령부' },
|
||||
},
|
||||
govBuilding: {
|
||||
executive: { icon: '🏛️', color: '#f59e0b', label: '행정부' },
|
||||
legislature: { icon: '🏛️', color: '#3b82f6', label: '입법부' },
|
||||
military_hq: { icon: '⭐', color: '#ef4444', label: '군사령부' },
|
||||
intelligence: { icon: '🔍', color: '#8b5cf6', label: '정보기관' },
|
||||
foreign: { icon: '🌐', color: '#06b6d4', label: '외교부' },
|
||||
maritime: { icon: '⚓', color: '#0ea5e9', label: '해양기관' },
|
||||
defense: { icon: '🛡️', color: '#dc2626', label: '국방부' },
|
||||
},
|
||||
nkLaunch: {
|
||||
icbm: { icon: '🚀', color: '#dc2626', label: 'ICBM 발사장' },
|
||||
irbm: { icon: '🚀', color: '#ef4444', label: 'IRBM/MRBM' },
|
||||
srbm: { icon: '🎯', color: '#f97316', label: '단거리탄도미사일' },
|
||||
slbm: { icon: '🔱', color: '#3b82f6', label: 'SLBM 발사' },
|
||||
cruise: { icon: '✈️', color: '#8b5cf6', label: '순항미사일' },
|
||||
artillery: { icon: '💥', color: '#eab308', label: '해안포/장사정포' },
|
||||
mlrs: { icon: '💥', color: '#f59e0b', label: '방사포(MLRS)' },
|
||||
},
|
||||
coastGuard: {
|
||||
hq: { icon: '🏢', color: '#3b82f6', label: '본청' },
|
||||
regional: { icon: '🏢', color: '#60a5fa', label: '지방청' },
|
||||
station: { icon: '🚔', color: '#22d3ee', label: '해양경찰서' },
|
||||
substation: { icon: '🏠', color: '#94a3b8', label: '파출소' },
|
||||
vts: { icon: '📡', color: '#f59e0b', label: 'VTS센터' },
|
||||
navy: { icon: '⚓', color: '#3b82f6', label: '해군부대' },
|
||||
},
|
||||
airport: {
|
||||
international: { icon: '✈️', color: '#a78bfa', label: '국제공항' },
|
||||
domestic: { icon: '🛩️', color: '#60a5fa', label: '국내공항' },
|
||||
military: { icon: '✈️', color: '#ef4444', label: '군용비행장' },
|
||||
},
|
||||
navWarning: {
|
||||
danger: { icon: '⚠️', color: '#ef4444', label: '위험' },
|
||||
caution: { icon: '⚠️', color: '#eab308', label: '주의' },
|
||||
info: { icon: 'ℹ️', color: '#3b82f6', label: '정보' },
|
||||
},
|
||||
piracy: {
|
||||
critical: { icon: '☠️', color: '#ef4444', label: '극고위험' },
|
||||
high: { icon: '☠️', color: '#f97316', label: '고위험' },
|
||||
moderate: { icon: '☠️', color: '#eab308', label: '주의' },
|
||||
},
|
||||
};
|
||||
|
||||
const KIND_DEFAULT: Record<string, { icon: string; color: string; label: string }> = {
|
||||
port: { icon: '⚓', color: '#3b82f6', label: '항구' },
|
||||
windFarm: { icon: '🌀', color: '#00bcd4', label: '풍력단지' },
|
||||
militaryBase: { icon: '🪖', color: '#ef4444', label: '군사시설' },
|
||||
govBuilding: { icon: '🏛️', color: '#f59e0b', label: '정부기관' },
|
||||
nkLaunch: { icon: '🚀', color: '#dc2626', label: '북한 발사장' },
|
||||
nkMissile: { icon: '💣', color: '#ef4444', label: '미사일 낙하' },
|
||||
coastGuard: { icon: '🚔', color: '#4dabf7', label: '해양경찰' },
|
||||
airport: { icon: '✈️', color: '#a78bfa', label: '공항' },
|
||||
navWarning: { icon: '⚠️', color: '#eab308', label: '항행경보' },
|
||||
piracy: { icon: '☠️', color: '#ef4444', label: '해적위험' },
|
||||
infra: { icon: '⚡', color: '#ffeb3b', label: '발전/변전' },
|
||||
hazard: { icon: '⚠️', color: '#ef4444', label: '위험시설' },
|
||||
cnFacility: { icon: '📍', color: '#ef4444', label: '중국시설' },
|
||||
jpFacility: { icon: '📍', color: '#f472b6', label: '일본시설' },
|
||||
};
|
||||
|
||||
// subType 키 결정
|
||||
const subKey = obj.type ?? obj.subType ?? obj.level ?? '';
|
||||
const subGroup = kind === 'cnFacility' || kind === 'jpFacility' ? 'overseas' : kind;
|
||||
const meta = SUB_META[subGroup]?.[subKey] ?? KIND_DEFAULT[kind] ?? { icon: '📍', color: '#64748b', label: kind };
|
||||
|
||||
// 국가 플래그
|
||||
const COUNTRY_FLAG: Record<string, string> = { CN: '🇨🇳', JP: '🇯🇵', KP: '🇰🇵', KR: '🇰🇷', TW: '🇹🇼' };
|
||||
const flag = kind === 'cnFacility' ? '🇨🇳' : kind === 'jpFacility' ? '🇯🇵' : COUNTRY_FLAG[obj.country] ?? '';
|
||||
const countryName = kind === 'cnFacility' ? '중국' : kind === 'jpFacility' ? '일본'
|
||||
: { CN: '중국', JP: '일본', KP: '북한', KR: '한국', TW: '대만' }[obj.country] ?? '';
|
||||
|
||||
// 이름 결정
|
||||
const title = obj.nameKo || obj.name || obj.launchNameKo || obj.title || kind;
|
||||
|
||||
return (
|
||||
<Popup longitude={lng} latitude={lat} anchor="bottom"
|
||||
onClose={onClose} closeOnClick={false}
|
||||
maxWidth="280px" className="gl-popup"
|
||||
>
|
||||
<div className="popup-body-sm" style={{ minWidth: 200 }}>
|
||||
{/* 컬러 헤더 */}
|
||||
<div className="popup-header" style={{ background: meta.color, color: '#000', gap: 6, padding: '4px 8px' }}>
|
||||
<span>{meta.icon}</span> {title}
|
||||
</div>
|
||||
{/* 배지 행 */}
|
||||
<div style={{ display: 'flex', gap: 4, marginBottom: 6, flexWrap: 'wrap' }}>
|
||||
<span style={{
|
||||
background: meta.color, color: '#000',
|
||||
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 700,
|
||||
}}>
|
||||
{meta.label}
|
||||
</span>
|
||||
{flag && (
|
||||
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>
|
||||
{flag} {countryName}
|
||||
</span>
|
||||
)}
|
||||
{kind === 'hazard' && (
|
||||
<span style={{
|
||||
background: 'rgba(239,68,68,0.15)', color: '#ef4444',
|
||||
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 600,
|
||||
border: '1px solid rgba(239,68,68,0.3)',
|
||||
}}>⚠️ 위험시설</span>
|
||||
)}
|
||||
{kind === 'port' && (
|
||||
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>
|
||||
{obj.type === 'major' ? '주요항' : '중소항'}
|
||||
</span>
|
||||
)}
|
||||
{kind === 'airport' && obj.intl && (
|
||||
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>국제선</span>
|
||||
)}
|
||||
</div>
|
||||
{/* 설명 */}
|
||||
{obj.description && (
|
||||
<div style={{ fontSize: 10, color: '#999', marginBottom: 4, lineHeight: 1.5 }}>{obj.description}</div>
|
||||
)}
|
||||
{obj.detail && (
|
||||
<div style={{ fontSize: 10, color: '#888', marginBottom: 4, lineHeight: 1.4 }}>{obj.detail}</div>
|
||||
)}
|
||||
{obj.note && (
|
||||
<div style={{ fontSize: 10, color: '#888', marginBottom: 4, lineHeight: 1.4 }}>{obj.note}</div>
|
||||
)}
|
||||
{/* 필드 그리드 */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{obj.operator && <div><span className="popup-label">운영: </span>{obj.operator}</div>}
|
||||
{obj.capacity && <div><span className="popup-label">규모: </span><strong>{obj.capacity}</strong></div>}
|
||||
{obj.output && <div><span className="popup-label">출력: </span><strong>{obj.output}</strong></div>}
|
||||
{obj.source && <div><span className="popup-label">연료: </span>{obj.source}</div>}
|
||||
{obj.capacityMW && <div><span className="popup-label">용량: </span><strong>{obj.capacityMW}MW</strong></div>}
|
||||
{obj.turbines && <div><span className="popup-label">터빈: </span>{obj.turbines}기</div>}
|
||||
{obj.status && <div><span className="popup-label">상태: </span>{obj.status}</div>}
|
||||
{obj.year && <div><span className="popup-label">연도: </span>{obj.year}년</div>}
|
||||
{obj.region && <div><span className="popup-label">지역: </span>{obj.region}</div>}
|
||||
{obj.org && <div><span className="popup-label">기관: </span>{obj.org}</div>}
|
||||
{obj.area && <div><span className="popup-label">해역: </span>{obj.area}</div>}
|
||||
{obj.altitude && <div><span className="popup-label">고도: </span>{obj.altitude}</div>}
|
||||
{obj.address && <div><span className="popup-label">주소: </span>{obj.address}</div>}
|
||||
{obj.recentUse && <div><span className="popup-label">최근 사용: </span>{obj.recentUse}</div>}
|
||||
{obj.recentIncidents != null && <div><span className="popup-label">최근 1년: </span><strong>{obj.recentIncidents}건</strong></div>}
|
||||
{obj.icao && <div><span className="popup-label">ICAO: </span>{obj.icao}</div>}
|
||||
{kind === 'nkMissile' && (
|
||||
<>
|
||||
{obj.typeKo && <div><span className="popup-label">미사일: </span>{obj.typeKo}</div>}
|
||||
{obj.date && <div><span className="popup-label">발사일: </span>{obj.date} {obj.time}</div>}
|
||||
{obj.distanceKm && <div><span className="popup-label">사거리: </span>{obj.distanceKm}km</div>}
|
||||
{obj.altitudeKm && <div><span className="popup-label">최고고도: </span>{obj.altitudeKm}km</div>}
|
||||
{obj.flightMin && <div><span className="popup-label">비행시간: </span>{obj.flightMin}분</div>}
|
||||
{obj.launchNameKo && <div><span className="popup-label">발사지: </span>{obj.launchNameKo}</div>}
|
||||
</>
|
||||
)}
|
||||
{obj.name && obj.nameKo && obj.name !== obj.nameKo && (
|
||||
<div><span className="popup-label">영문: </span>{obj.name}</div>
|
||||
)}
|
||||
<div style={{ fontSize: 9, color: '#666', marginTop: 2 }}>
|
||||
{lat.toFixed(4)}°N, {lng.toFixed(4)}°E
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
|
||||
export { StaticFacilityPopup };
|
||||
불러오는 중...
Reference in New Issue
Block a user