import { useState, useRef, useEffect } from 'react' interface SatRequest { id: string zone: string zoneCoord: string zoneArea: string satellite: string requestDate: string expectedReceive: string resolution: string status: '촬영중' | '대기' | '완료' provider?: string purpose?: string requester?: string } const satRequests: SatRequest[] = [ { id: 'SAT-004', zone: '제주 서귀포 해상 (유출 해역 중심)', zoneCoord: '33.24°N 126.50°E', zoneArea: '15km²', satellite: 'KOMPSAT-3A', requestDate: '02-20 08:14', expectedReceive: '02-20 14:30', resolution: '0.5m', status: '촬영중', provider: 'KARI', purpose: '유출유 확산 모니터링', requester: '방제과 김해양' }, { id: 'SAT-005', zone: '가파도 북쪽 해안선', zoneCoord: '33.17°N 126.27°E', zoneArea: '8km²', satellite: 'KOMPSAT-3', requestDate: '02-20 09:02', expectedReceive: '02-21 09:00', resolution: '1.0m', status: '대기', provider: 'KARI', purpose: '해안선 오염 확인', requester: '방제과 이민수' }, { id: 'SAT-006', zone: '마라도 주변 해역', zoneCoord: '33.11°N 126.27°E', zoneArea: '12km²', satellite: 'Sentinel-2', requestDate: '02-20 09:30', expectedReceive: '02-21 11:00', resolution: '10m', status: '대기', provider: 'ESA Copernicus', purpose: '수질 분석용 다분광 촬영', requester: '환경분석팀 박수진' }, { id: 'SAT-007', zone: '대정읍 해안 오염 확산 구역', zoneCoord: '33.21°N 126.10°E', zoneArea: '20km²', satellite: 'KOMPSAT-3A', requestDate: '02-20 10:05', expectedReceive: '02-22 08:00', resolution: '0.5m', status: '대기', provider: 'KARI', purpose: '확산 예측 모델 검증', requester: '방제과 김해양' }, { id: 'SAT-003', zone: '제주 남방 100해리 해상', zoneCoord: '33.00°N 126.50°E', zoneArea: '25km²', satellite: 'Sentinel-1', requestDate: '02-19 14:00', expectedReceive: '02-19 23:00', resolution: '20m', status: '완료', provider: 'ESA Copernicus', purpose: 'SAR 유막 탐지', requester: '환경분석팀 박수진' }, { id: 'SAT-002', zone: '여수 오동도 인근 해역', zoneCoord: '34.73°N 127.68°E', zoneArea: '18km²', satellite: 'KOMPSAT-3A', requestDate: '02-18 11:30', expectedReceive: '02-18 17:45', resolution: '0.5m', status: '완료', provider: 'KARI', purpose: '유출 초기 범위 확인', requester: '방제과 김해양' }, { id: 'SAT-001', zone: '통영 해역 남측', zoneCoord: '34.85°N 128.43°E', zoneArea: '30km²', satellite: 'Sentinel-1', requestDate: '02-17 09:00', expectedReceive: '02-17 21:00', resolution: '20m', status: '완료', provider: 'ESA Copernicus', purpose: '야간 SAR 유막 모니터링', requester: '환경분석팀 박수진' }, ] const satellites = [ { name: 'KOMPSAT-3A', desc: '해상도 0.5m · 광학 / IR · 촬영 가능', status: '가용', statusColor: 'var(--green)', borderColor: 'rgba(34,197,94,.2)', pulse: true }, { name: 'KOMPSAT-3', desc: '해상도 1.0m · 광학 · 임무 중', status: '임무중', statusColor: 'var(--yellow)', borderColor: 'rgba(234,179,8,.2)', pulse: true }, { name: 'Sentinel-1 (ESA)', desc: '해상도 20m · SAR · 야간/우천 가능', status: '가용', statusColor: 'var(--green)', borderColor: 'var(--bd)', pulse: false }, { name: 'Sentinel-2 (ESA)', desc: '해상도 10m · 다분광 · 수질 분석 적합', status: '가용', statusColor: 'var(--green)', borderColor: 'var(--bd)', pulse: false }, ] const passSchedules = [ { time: '14:10 – 14:24', desc: 'KOMPSAT-3A 패스 (제주 남방)', today: true }, { time: '16:55 – 17:08', desc: 'Sentinel-1 패스 (제주 전역)', today: true }, { time: '내일 09:12', desc: 'KOMPSAT-3 패스 (가파도~마라도)', today: false }, { time: '내일 10:40', desc: 'Sentinel-2 패스 (제주 서측)', today: false }, ] // UP42 위성 카탈로그 데이터 const up42Satellites = [ { id: 'mwl-hd15', name: 'Maxar WorldView Legion HD15', res: '0.3m', type: 'optical' as const, color: '#3b82f6', cloud: 15 }, { id: 'pneo-hd15', name: 'Pléiades Neo HD15', res: '0.3m', type: 'optical' as const, color: '#06b6d4', cloud: 10 }, { id: 'mwl', name: 'Maxar WorldView Legion', res: '0.5m', type: 'optical' as const, color: '#3b82f6', cloud: 20 }, { id: 'mwv3', name: 'Maxar WorldView-3', res: '0.5m', type: 'optical' as const, color: '#3b82f6', cloud: 20 }, { id: 'pneo', name: 'Pléiades Neo', res: '0.5m', type: 'optical' as const, color: '#06b6d4', cloud: 15 }, { id: 'bj3n', name: 'Beijing-3N', res: '0.5m', type: 'optical' as const, color: '#f97316', cloud: 20, delay: true }, { id: 'skysat', name: 'SkySat', res: '0.7m', type: 'optical' as const, color: '#22c55e', cloud: 15 }, { id: 'kmp3a', name: 'KOMPSAT-3A', res: '0.5m', type: 'optical' as const, color: '#a855f7', cloud: 10 }, { id: 'kmp3', name: 'KOMPSAT-3', res: '1.0m', type: 'optical' as const, color: '#a855f7', cloud: 15 }, { id: 'spot7', name: 'SPOT 7', res: '1.5m', type: 'optical' as const, color: '#eab308', cloud: 20 }, { id: 's2', name: 'Sentinel-2', res: '10m', type: 'optical' as const, color: '#ec4899', cloud: 20 }, { id: 's1', name: 'Sentinel-1 SAR', res: '20m', type: 'sar' as const, color: '#f59e0b', cloud: 0 }, { id: 'alos2', name: 'ALOS-2 PALSAR-2', res: '3m', type: 'sar' as const, color: '#f59e0b', cloud: 0 }, { id: 'rcm', name: 'RCM (Radarsat)', res: '3m', type: 'sar' as const, color: '#f59e0b', cloud: 0 }, { id: 'srtm', name: 'SRTM DEM', res: '30m', type: 'elevation' as const, color: '#64748b', cloud: 0 }, { id: 'cop-dem', name: 'Copernicus DEM', res: '10m', type: 'elevation' as const, color: '#64748b', cloud: 0 }, ] const up42Passes = [ { sat: 'KOMPSAT-3A', time: '오늘 14:10–14:24', res: '0.5m', cloud: '≤10%', note: '최우선 추천', color: '#a855f7' }, { sat: 'Pléiades Neo', time: '오늘 14:38–14:52', res: '0.3m', cloud: '≤15%', note: '초고해상도', color: '#06b6d4' }, { sat: 'Sentinel-1 SAR', time: '오늘 16:55–17:08', res: '20m', cloud: '야간/우천 가능', note: 'SAR', color: '#f59e0b' }, { sat: 'KOMPSAT-3', time: '내일 09:12', res: '1.0m', cloud: '≤15%', note: '', color: '#a855f7' }, { sat: 'Maxar WV-3', time: '내일 13:20', res: '0.5m', cloud: '≤20%', note: '', color: '#3b82f6' }, ] type SatModalPhase = 'none' | 'provider' | 'blacksky' | 'up42' export function SatelliteRequest() { const [statusFilter, setStatusFilter] = useState('전체') const [modalPhase, setModalPhase] = useState('none') const [selectedRequest, setSelectedRequest] = useState(null) const [showMoreCompleted, setShowMoreCompleted] = useState(false) // UP42 sub-tab const [up42SubTab, setUp42SubTab] = useState<'optical' | 'sar' | 'elevation'>('optical') const [up42SelSat, setUp42SelSat] = useState(null) const [up42SelPass, setUp42SelPass] = useState(null) const modalRef = useRef(null) useEffect(() => { const handler = (e: MouseEvent) => { if (modalRef.current && !modalRef.current.contains(e.target as Node)) { setModalPhase('none') } } if (modalPhase !== 'none') document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, [modalPhase]) const allRequests = showMoreCompleted ? satRequests : satRequests.filter(r => r.status !== '완료' || r.id === 'SAT-003') const filtered = allRequests.filter(r => { if (statusFilter === '전체') return true if (statusFilter === '대기') return r.status === '대기' if (statusFilter === '진행') return r.status === '촬영중' if (statusFilter === '완료') return r.status === '완료' return true }) const statusBadge = (s: SatRequest['status']) => { if (s === '촬영중') return ( 촬영중 ) if (s === '대기') return ( ⏳ 대기 ) return ( ✅ 완료 ) } const stats = [ { value: '3', label: '요청 대기', color: 'var(--blue)' }, { value: '1', label: '촬영 진행 중', color: 'var(--yellow)' }, { value: '7', label: '수신 완료', color: 'var(--green)' }, { value: '0.5m', label: '최고 해상도', color: 'var(--cyan)' }, ] const filters = ['전체', '대기', '진행', '완료'] const up42Filtered = up42Satellites.filter(s => s.type === up42SubTab) // ── 섹션 헤더 헬퍼 (BlackSky 폼) ── const sectionHeader = (num: number, label: string) => (
{num}
{label}
) const bsInput = "w-full px-3 py-2 rounded-md text-[11px] font-korean outline-none box-border" const bsInputStyle = { border: '1px solid #21262d', background: '#161b22', color: '#e2e8f0' } return (
{/* 헤더 */}
🛰
위성 촬영 요청
위성 촬영 임무를 요청하고 수신 현황을 관리합니다
{/* 요약 통계 */}
{stats.map((s, i) => (
{s.value}
{s.label}
))}
{/* 요청 목록 */}
📋 위성 요청 목록
{filters.map(f => ( ))}
{/* 헤더 행 */}
{['번호', '촬영 구역', '위성', '요청일시', '예상수신', '해상도', '상태'].map(h => (
{h}
))}
{/* 데이터 행 */} {filtered.map(r => (
setSelectedRequest(selectedRequest?.id === r.id ? null : r)} className="grid gap-0 px-4 py-3 border-b items-center cursor-pointer hover:bg-bg-hover/30 transition-colors" style={{ gridTemplateColumns: '60px 1fr 100px 100px 120px 80px 90px', borderColor: 'rgba(255,255,255,.04)', background: selectedRequest?.id === r.id ? 'rgba(99,102,241,.06)' : r.status === '촬영중' ? 'rgba(234,179,8,.03)' : 'transparent', opacity: r.status === '완료' ? 0.7 : 1, }} >
{r.id}
{r.zone}
{r.zoneCoord} · {r.zoneArea}
{r.satellite}
{r.requestDate}
{r.expectedReceive}
{r.resolution}
{statusBadge(r.status)}
{/* 상세 정보 패널 */} {selectedRequest?.id === r.id && (
{[ ['제공자', r.provider || '-'], ['요청 목적', r.purpose || '-'], ['요청자', r.requester || '-'], ['촬영 면적', r.zoneArea], ].map(([k, v], i) => (
{k}
{v}
))}
{r.status === '완료' && ( )} {r.status === '대기' && ( )}
)}
))}
setShowMoreCompleted(!showMoreCompleted)} className="text-center py-2.5 text-[10px] text-text-3 font-korean cursor-pointer hover:text-text-2 transition-colors" > {showMoreCompleted ? '▲ 완료 목록 접기' : '▼ 이전 완료 목록 더보기 (6건)'}
{/* 위성 궤도 정보 */}
{/* 가용 위성 현황 */}
🛰 가용 위성 현황
{satellites.map((sat, i) => (
{sat.name}
{sat.desc}
{sat.status}
))}
{/* 오늘 촬영 가능 시간 */}
⏰ 오늘 촬영 가능 시간 (KST)
{passSchedules.map((ps, i) => (
{ps.time} {ps.desc}
))}
{/* ═══ 모달: 제공자 선택 ═══ */} {modalPhase !== 'none' && (
{/* ── 제공자 선택 ── */} {modalPhase === 'provider' && (
{/* 헤더 */}
🛰
위성 촬영 요청 — 제공자 선택
요청할 위성 서비스 제공자를 선택하세요
{/* 제공자 카드 */}
{/* BlackSky (Maxar) */}
setModalPhase('blacksky')} className="cursor-pointer bg-bg-2 border border-border rounded-xl p-5 relative overflow-hidden hover:border-[rgba(99,102,241,.5)] hover:bg-[rgba(99,102,241,.04)] transition-all" >
BSky
BlackSky
Maxar Electro-Optical API
API 연결됨
{[ ['유형', 'EO (광학)', '#818cf8'], ['해상도', '~1m', 'var(--t1)'], ['재방문', '≤1시간', 'var(--t1)'], ['납기', '90분 이내', '#22c55e'], ].map(([k, v, c], i) => (
{k}
{v}
))}
고빈도 소형위성 군집 기반 긴급 촬영. 해양 사고 현장 신속 모니터링에 최적화. Dawn-to-Dusk 촬영 가능.
API: eapi.maxar.com/e1so/rapidoc
{/* UP42 (EO + SAR) */}
setModalPhase('up42')} className="cursor-pointer bg-bg-2 border border-border rounded-xl p-5 relative overflow-hidden hover:border-[rgba(59,130,246,.5)] hover:bg-[rgba(59,130,246,.04)] transition-all" >
up42
UP42 — EO + SAR
Optical · SAR · Elevation 통합 마켓플레이스
API 연결됨
{[ ['유형', 'EO + SAR', '#60a5fa'], ['해상도', '0.3~5m', 'var(--t1)'], ['위성 수', '16+ 컬렉션', 'var(--t1)'], ['야간/악천후', 'SAR 가능', '#22c55e'], ].map(([k, v, c], i) => (
{k}
{v}
))}
{['Pléiades Neo', 'SPOT 6/7'].map((t, i) => ( {t} ))} {['TerraSAR-X', 'Capella SAR', 'ICEYE'].map((t, i) => ( {t} ))} +11 more
광학(EO) + 합성개구레이더(SAR) 통합 마켓. 야간·악천후 시 SAR 활용. 다중 위성 소스 자동 최적 선택.
API: up42.com
{/* 하단 */}
💡 긴급 촬영: BlackSky 권장 (90분 납기) · 야간/악천후: UP42 SAR 권장
)} {/* ── BlackSky 긴급 촬영 요청 ── */} {modalPhase === 'blacksky' && (
{/* 헤더 */}
BSky
BlackSky — 긴급 위성 촬영 요청
Maxar E1SO RapiDoc API · 고빈도 긴급 태스킹
API Docs ↗
{/* 본문 */}
{/* API 상태 */}
API Connected eapi.maxar.com/e1so/rapidoc · Latency: 142ms Quota: 47/50 요청 잔여
{/* ① 태스킹 유형 */}
{sectionHeader(1, '태스킹 유형 · 우선순위')}
{/* ② AOI 지정 */}
{sectionHeader(2, '관심 영역 (AOI)')}
{/* ③ 촬영 기간 */}
{sectionHeader(3, '촬영 기간 · 반복')}
{/* ④ 산출물 설정 */}
{sectionHeader(4, '산출물 설정')}
{[ { label: '유출유 탐지 분석 (자동)', checked: true }, { label: 'GIS 상황판 자동 오버레이', checked: true }, { label: '변화탐지 (Change Detection)', checked: false }, { label: '웹훅 알림', checked: false }, ].map((opt, i) => ( ))}
{/* ⑤ 연계 사고 · 비고 */}
{sectionHeader(5, '연계 사고 · 비고')}