import { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import { fetchGroupPolygons } from '../services/vesselAnalysis'; import type { GroupPolygonDto } from '../services/vesselAnalysis'; const POLL_INTERVAL_MS = 5 * 60_000; // 5분 /** 같은 groupKey의 서브클러스터를 하나로 합산 (멤버 합산, 가장 큰 폴리곤 사용) */ function mergeByGroupKey(groups: GroupPolygonDto[]): GroupPolygonDto[] { const byKey = new Map(); for (const g of groups) { const list = byKey.get(g.groupKey) ?? []; list.push(g); byKey.set(g.groupKey, list); } const result: GroupPolygonDto[] = []; for (const [, items] of byKey) { if (items.length === 1) { result.push(items[0]); continue; } const seen = new Set(); const allMembers: GroupPolygonDto['members'] = []; for (const item of items) for (const m of item.members) { if (!seen.has(m.mmsi)) { seen.add(m.mmsi); allMembers.push(m); } } const biggest = items.reduce((a, b) => a.memberCount >= b.memberCount ? a : b); result.push({ ...biggest, subClusterId: 0, members: allMembers, memberCount: allMembers.length, }); } return result; } export interface UseGroupPolygonsResult { fleetGroups: GroupPolygonDto[]; gearInZoneGroups: GroupPolygonDto[]; gearOutZoneGroups: GroupPolygonDto[]; allGroups: GroupPolygonDto[]; isLoading: boolean; lastUpdated: number; } const EMPTY: UseGroupPolygonsResult = { fleetGroups: [], gearInZoneGroups: [], gearOutZoneGroups: [], allGroups: [], isLoading: false, lastUpdated: 0, }; export function useGroupPolygons(enabled: boolean): UseGroupPolygonsResult { const [allGroups, setAllGroups] = useState([]); const [isLoading, setIsLoading] = useState(false); const [lastUpdated, setLastUpdated] = useState(0); const timerRef = useRef>(); const load = useCallback(async () => { setIsLoading(true); try { const groups = await fetchGroupPolygons(); setAllGroups(groups); setLastUpdated(Date.now()); } catch { // 네트워크 오류 시 기존 데이터 유지 } finally { setIsLoading(false); } }, []); useEffect(() => { if (!enabled) return; load(); timerRef.current = setInterval(load, POLL_INTERVAL_MS); return () => clearInterval(timerRef.current); }, [enabled, load]); const fleetGroups = useMemo( () => mergeByGroupKey(allGroups.filter(g => g.groupType === 'FLEET')), [allGroups], ); const gearInZoneGroups = useMemo( () => mergeByGroupKey(allGroups.filter(g => g.groupType === 'GEAR_IN_ZONE')), [allGroups], ); const gearOutZoneGroups = useMemo( () => mergeByGroupKey(allGroups.filter(g => g.groupType === 'GEAR_OUT_ZONE')), [allGroups], ); if (!enabled) return EMPTY; return { fleetGroups, gearInZoneGroups, gearOutZoneGroups, allGroups, isLoading, lastUpdated }; }