import { useEffect, useState, useCallback } from 'react'; import { Loader2, RefreshCw, MapPin } from 'lucide-react'; import { Card, CardContent } from '@shared/components/ui/card'; import { Badge } from '@shared/components/ui/badge'; import { fetchGroups, type GearGroupItem } from '@/services/vesselAnalysisApi'; import { getGearGroupTypeIntent, getGearGroupTypeLabel } from '@shared/constants/gearGroupTypes'; import { getParentResolutionIntent, getParentResolutionLabel } from '@shared/constants/parentResolutionStatuses'; import { useSettingsStore } from '@stores/settingsStore'; import { useTranslation } from 'react-i18next'; /** * iran 백엔드의 실시간 어구/선단 그룹을 표시. * - GET /api/vessel-analysis/groups * - 자체 DB의 ParentResolution이 합성되어 있음 */ export function RealGearGroups() { const { t: tc } = useTranslation('common'); const lang = useSettingsStore((s) => s.language); const [items, setItems] = useState([]); const [available, setAvailable] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [filterType, setFilterType] = useState(''); const load = useCallback(async () => { setLoading(true); setError(''); try { const res = await fetchGroups(); setItems(res.items); setAvailable(res.serviceAvailable); } catch (e: unknown) { setError(e instanceof Error ? e.message : 'unknown'); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const filtered = filterType ? items.filter((i) => i.groupType === filterType) : items; const stats = { total: items.length, fleet: items.filter((i) => i.groupType === 'FLEET').length, gearInZone: items.filter((i) => i.groupType === 'GEAR_IN_ZONE').length, gearOutZone: items.filter((i) => i.groupType === 'GEAR_OUT_ZONE').length, confirmed: items.filter((i) => i.resolution?.status === 'MANUAL_CONFIRMED').length, }; return (
실시간 어구/선단 그룹 (iran 백엔드) {!available && 미연결}
GET /api/vessel-analysis/groups · 자체 DB의 운영자 결정(resolution) 합성됨
{/* 통계 */}
{error &&
에러: {error}
} {loading &&
} {!loading && (
{filtered.length === 0 && ( )} {filtered.slice(0, 100).map((g) => ( ))}
유형 그룹 키 서브 멤버 면적(NM²) 중심 좌표 운영자 결정 스냅샷 시각
데이터가 없습니다.
{getGearGroupTypeLabel(g.groupType, tc, lang)} {g.groupKey} {g.subClusterId} {g.memberCount} {g.areaSqNm?.toFixed(2)} {g.centerLat?.toFixed(3)}, {g.centerLon?.toFixed(3)} {g.resolution ? ( {getParentResolutionLabel(g.resolution.status, tc, lang)} ) : -} {g.snapshotTime ? new Date(g.snapshotTime).toLocaleTimeString('ko-KR') : '-'}
{filtered.length > 100 && (
상위 100건만 표시 (전체 {filtered.length}건)
)}
)}
); } function StatBox({ label, value, color }: { label: string; value: number; color: string }) { return (
{label}
{value}
); }