import { useEffect, useState, useCallback } from 'react'; import { Tag, X, Loader2 } from 'lucide-react'; import { Card, CardContent } from '@shared/components/ui/card'; import { Badge } from '@shared/components/ui/badge'; import { useAuth } from '@/app/auth/AuthContext'; import { fetchLabelSessions, createLabelSession, cancelLabelSession, type LabelSession as LabelSessionType, } from '@/services/parentInferenceApi'; import { formatDateTime } from '@shared/utils/dateFormat'; /** * 모선 추론 학습 세션 페이지. * 운영자가 정답 라벨링 → prediction 모델 학습 데이터로 활용. * * 권한: parent-inference-workflow:label-session (READ + CREATE + UPDATE) */ const STATUS_COLORS: Record = { ACTIVE: 'bg-green-500/20 text-green-400', CANCELLED: 'bg-gray-500/20 text-gray-400', COMPLETED: 'bg-blue-500/20 text-blue-400', }; export function LabelSession() { const { hasPermission } = useAuth(); const canCreate = hasPermission('parent-inference-workflow:label-session', 'CREATE'); const canUpdate = hasPermission('parent-inference-workflow:label-session', 'UPDATE'); const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [filter, setFilter] = useState(''); const [busy, setBusy] = useState(null); // 신규 세션 const [groupKey, setGroupKey] = useState(''); const [subCluster, setSubCluster] = useState('1'); const [labelMmsi, setLabelMmsi] = useState(''); const load = useCallback(async () => { setLoading(true); setError(''); try { const res = await fetchLabelSessions(filter || undefined, 0, 50); setItems(res.content); } catch (e: unknown) { setError(e instanceof Error ? e.message : 'unknown'); } finally { setLoading(false); } }, [filter]); useEffect(() => { load(); }, [load]); const handleCreate = async () => { if (!canCreate || !groupKey || !labelMmsi) return; setBusy(-1); try { await createLabelSession(groupKey, parseInt(subCluster, 10), { labelParentMmsi: labelMmsi, anchorSnapshot: { source: 'manual', timestamp: new Date().toISOString() }, }); setGroupKey(''); setLabelMmsi(''); await load(); } catch (e: unknown) { alert('실패: ' + (e instanceof Error ? e.message : 'unknown')); } finally { setBusy(null); } }; const handleCancel = async (id: number) => { if (!canUpdate) return; if (!confirm('세션을 취소하시겠습니까?')) return; setBusy(id); try { await cancelLabelSession(id, '운영자 취소'); await load(); } catch (e: unknown) { alert('실패: ' + (e instanceof Error ? e.message : 'unknown')); } finally { setBusy(null); } }; return (

학습 세션

정답 라벨링 → prediction 모델 학습 데이터로 활용

신규 학습 세션 등록 {!canCreate && 권한 없음}
setGroupKey(e.target.value)} placeholder="group_key" className="flex-1 bg-surface-overlay border border-border rounded px-3 py-1.5 text-xs" disabled={!canCreate} /> setSubCluster(e.target.value)} placeholder="sub" className="w-24 bg-surface-overlay border border-border rounded px-3 py-1.5 text-xs" disabled={!canCreate} /> setLabelMmsi(e.target.value)} placeholder="정답 parent MMSI" className="w-48 bg-surface-overlay border border-border rounded px-3 py-1.5 text-xs" disabled={!canCreate} />
{error &&
에러: {error}
} {loading && (
)} {!loading && ( {items.length === 0 && ( )} {items.map((it) => ( ))}
ID Group Key Sub 정답 MMSI 상태 생성자 시작 액션
학습 세션이 없습니다.
{it.id} {it.groupKey} {it.subClusterId} {it.labelParentMmsi} {it.status} {it.createdByAcnt || '-'} {formatDateTime(it.activeFrom)} {it.status === 'ACTIVE' && ( )}
)}
); }