diff --git a/frontend/src/features/admin/NoticeManagement.tsx b/frontend/src/features/admin/NoticeManagement.tsx
index d3c8e7a..7608264 100644
--- a/frontend/src/features/admin/NoticeManagement.tsx
+++ b/frontend/src/features/admin/NoticeManagement.tsx
@@ -4,6 +4,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@shared/components/ui/
import { Badge } from '@shared/components/ui/badge';
import { Button } from '@shared/components/ui/button';
import { PageContainer, PageHeader } from '@shared/components/layout';
+import type { BadgeIntent } from '@lib/theme/variants';
import {
Bell, Plus, Edit2, Trash2, Eye, EyeOff, Calendar,
Users, Megaphone, AlertTriangle, Info, Search, Filter,
@@ -125,10 +126,10 @@ export function NoticeManagement() {
}));
};
- const getStatus = (n: SystemNotice) => {
- if (n.endDate < now) return { label: '종료', color: 'bg-muted text-muted-foreground' };
- if (n.startDate > now) return { label: '예약', color: 'bg-blue-500/20 text-blue-400' };
- return { label: '노출 중', color: 'bg-green-500/20 text-green-400' };
+ const getStatus = (n: SystemNotice): { label: string; intent: BadgeIntent } => {
+ if (n.endDate < now) return { label: '종료', intent: 'muted' };
+ if (n.startDate > now) return { label: '예약', intent: 'info' };
+ return { label: '노출 중', intent: 'success' };
};
// KPI
@@ -203,7 +204,7 @@ export function NoticeManagement() {
return (
|
- {status.label}
+ {status.label}
|
diff --git a/frontend/src/features/admin/SystemConfig.tsx b/frontend/src/features/admin/SystemConfig.tsx
index 1453e3c..b97f823 100644
--- a/frontend/src/features/admin/SystemConfig.tsx
+++ b/frontend/src/features/admin/SystemConfig.tsx
@@ -4,6 +4,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@shared/components/ui/
import { Badge } from '@shared/components/ui/badge';
import { Button } from '@shared/components/ui/button';
import { PageContainer, PageHeader } from '@shared/components/layout';
+import type { BadgeIntent } from '@lib/theme/variants';
import {
Settings, Database, Search, ChevronDown, ChevronRight,
Map, Fish, Anchor, Ship, Globe, BarChart3, Download,
@@ -269,13 +270,10 @@ export function SystemConfig() {
| {a.code} |
- {a.major}
+ {(() => {
+ const intent: BadgeIntent = a.major === '서해' ? 'info' : a.major === '남해' ? 'success' : a.major === '동해' ? 'purple' : a.major === '제주' ? 'high' : 'cyan';
+ return {a.major};
+ })()}
|
{a.mid} |
{a.name} |
@@ -359,25 +357,16 @@ export function SystemConfig() {
| {f.code} |
- {f.major}
+ {(() => {
+ const intent: BadgeIntent = f.major === '근해어업' ? 'info' : f.major === '연안어업' ? 'success' : f.major === '양식어업' ? 'cyan' : f.major === '원양어업' ? 'purple' : f.major === '구획어업' ? 'high' : f.major === '마을어업' ? 'warning' : 'muted';
+ return {f.major};
+ })()}
|
{f.mid} |
{f.name} |
{f.target} |
- {f.permit}
+ {f.permit}
|
{f.law} |
@@ -410,15 +399,10 @@ export function SystemConfig() {
| {v.code} |
- {v.major}
+ {(() => {
+ const intent: BadgeIntent = v.major === '어선' ? 'info' : v.major === '여객선' ? 'success' : v.major === '화물선' ? 'high' : v.major === '유조선' ? 'critical' : v.major === '관공선' ? 'purple' : v.major === '함정' ? 'cyan' : 'muted';
+ return {v.major};
+ })()}
|
{v.mid} |
{v.name} |
diff --git a/frontend/src/features/ai-operations/AIModelManagement.tsx b/frontend/src/features/ai-operations/AIModelManagement.tsx
index 8c20d1e..e7b19df 100644
--- a/frontend/src/features/ai-operations/AIModelManagement.tsx
+++ b/frontend/src/features/ai-operations/AIModelManagement.tsx
@@ -13,6 +13,8 @@ import {
} from 'lucide-react';
import { AreaChart as EcAreaChart, BarChart as EcBarChart, PieChart as EcPieChart } from '@lib/charts';
import { getEngineSeverityIntent, getEngineSeverityLabel } from '@shared/constants/engineSeverities';
+import { getStatusIntent } from '@shared/constants/statusIntent';
+import type { BadgeIntent } from '@lib/theme/variants';
import { useSettingsStore } from '@stores/settingsStore';
/*
@@ -59,8 +61,7 @@ const modelColumns: DataColumn[] = [
{ key: 'status', label: '상태', width: '70px', align: 'center', sortable: true,
render: (v) => {
const s = v as string;
- const c = s === '운영중' ? 'bg-green-500/20 text-green-400' : s === '테스트' ? 'bg-blue-500/20 text-blue-400' : s === '대기' ? 'bg-yellow-500/20 text-yellow-400' : 'bg-muted text-hint';
- return {s};
+ return {s};
},
},
{ key: 'accuracy', label: 'Accuracy', width: '80px', align: 'right', sortable: true, render: (v) => {v as number}% },
@@ -179,8 +180,8 @@ const gearColumns: DataColumn[] = [
{ key: 'risk', label: '위험도', width: '70px', align: 'center', sortable: true,
render: (v) => {
const r = v as string;
- const c = r === '고위험' ? 'bg-red-500/20 text-red-400' : r === '중위험' ? 'bg-yellow-500/20 text-yellow-400' : 'bg-green-500/20 text-green-400';
- return {r};
+ const intent: BadgeIntent = r === '고위험' ? 'critical' : r === '중위험' ? 'warning' : 'success';
+ return {r};
},
},
{ key: 'speed', label: '탐지 속도', width: '90px', align: 'center', render: (v) => {v as string} },
@@ -625,7 +626,6 @@ export function AIModelManagement() {
{/* 7대 엔진 카드 */}
{DETECTION_ENGINES.map((eng) => {
- const stColor = eng.status === '운영중' ? 'bg-green-500/20 text-green-400' : eng.status === '테스트' ? 'bg-blue-500/20 text-blue-400' : 'bg-muted text-muted-foreground';
return (
@@ -637,7 +637,7 @@ export function AIModelManagement() {
{eng.name}
- {eng.status}
+ {eng.status}
{eng.purpose}
{eng.detail}
@@ -850,14 +850,14 @@ export function AIModelManagement() {
].map((api, i) => (
|
- {api.method}
+ {api.method}
|
{api.endpoint} |
{api.unit} |
{api.desc} |
{api.sfr} |
- {api.status}
+ {api.status}
|
))}
diff --git a/frontend/src/features/ai-operations/MLOpsPage.tsx b/frontend/src/features/ai-operations/MLOpsPage.tsx
index 8d8661a..e82fa1a 100644
--- a/frontend/src/features/ai-operations/MLOpsPage.tsx
+++ b/frontend/src/features/ai-operations/MLOpsPage.tsx
@@ -4,6 +4,7 @@ import { Card, CardContent } from '@shared/components/ui/card';
import { Badge } from '@shared/components/ui/badge';
import { PageContainer, PageHeader } from '@shared/components/layout';
import { getModelStatusIntent, getQualityGateIntent, getExperimentIntent, MODEL_STATUSES, QUALITY_GATE_STATUSES, EXPERIMENT_STATUSES } from '@shared/constants/modelDeploymentStatuses';
+import { getStatusIntent } from '@shared/constants/statusIntent';
import {
Cpu, Brain, Database, GitBranch, Activity, RefreshCw, Server, Shield,
FileText, Settings, Layers, Globe, Lock, BarChart3, Code, Play, Square,
@@ -266,7 +267,7 @@ export function MLOpsPage() {
{d.latency} |
{d.falseAlarm} |
{d.rps} |
- {d.status} |
+ {d.status} |
{d.date} |
))}
@@ -390,7 +391,7 @@ export function MLOpsPage() {
{j.id}
{j.model}
- {j.status}
+ {j.status}
{j.elapsed}
diff --git a/frontend/src/features/dashboard/Dashboard.tsx b/frontend/src/features/dashboard/Dashboard.tsx
index 1423dfb..98f3cd7 100644
--- a/frontend/src/features/dashboard/Dashboard.tsx
+++ b/frontend/src/features/dashboard/Dashboard.tsx
@@ -26,7 +26,7 @@ import {
import { toDateParam, formatDate, formatTime } from '@shared/utils/dateFormat';
import { getViolationColor, getViolationLabel } from '@shared/constants/violationTypes';
import { ALERT_LEVELS, type AlertLevel, getAlertLevelLabel, getAlertLevelIntent } from '@shared/constants/alertLevels';
-import { getPatrolStatusClasses, getPatrolStatusLabel } from '@shared/constants/patrolStatuses';
+import { getPatrolStatusIntent, getPatrolStatusLabel } from '@shared/constants/patrolStatuses';
import { getKpiUi } from '@shared/constants/kpiUiMap';
import { useSettingsStore } from '@stores/settingsStore';
@@ -124,7 +124,7 @@ function PatrolStatusBadge({ status }: { status: string }) {
const { t: tc } = useTranslation('common');
const lang = useSettingsStore((s) => s.language);
return (
-
+
{getPatrolStatusLabel(status, tc, lang)}
);
diff --git a/frontend/src/features/detection/GearIdentification.tsx b/frontend/src/features/detection/GearIdentification.tsx
index 828b6b3..9900a4f 100644
--- a/frontend/src/features/detection/GearIdentification.tsx
+++ b/frontend/src/features/detection/GearIdentification.tsx
@@ -489,30 +489,30 @@ function SelectField({ value, onChange, options }: {
}
function ResultBadge({ origin, confidence }: { origin: Origin; confidence: Confidence }) {
- const colors: Record = {
- china: 'bg-red-500/20 text-red-400 border-red-500/40',
- korea: 'bg-blue-500/20 text-blue-400 border-blue-500/40',
- uncertain: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/40',
+ const intents: Record = {
+ china: 'critical',
+ korea: 'info',
+ uncertain: 'warning',
};
const labels: Record = { china: '중국어선 어구', korea: '한국어선 어구', uncertain: '판별 불가' };
const confLabels: Record = { high: '높음', medium: '보통', low: '낮음' };
return (
- {labels[origin]}
+ {labels[origin]}
신뢰도: {confLabels[confidence]}
);
}
function AlertBadge({ level }: { level: string }) {
- const styles: Record = {
- CRITICAL: 'bg-red-600/20 text-red-400 border-red-600/40',
- HIGH: 'bg-orange-500/20 text-orange-400 border-orange-500/40',
- MEDIUM: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/40',
- LOW: 'bg-blue-500/20 text-blue-400 border-blue-500/40',
+ const intents: Record = {
+ CRITICAL: 'critical',
+ HIGH: 'high',
+ MEDIUM: 'warning',
+ LOW: 'info',
};
- return {level};
+ return {level};
}
// ─── 어구 비교 레퍼런스 테이블 ──────────
diff --git a/frontend/src/features/enforcement/EnforcementHistory.tsx b/frontend/src/features/enforcement/EnforcementHistory.tsx
index 418fe02..79838e9 100644
--- a/frontend/src/features/enforcement/EnforcementHistory.tsx
+++ b/frontend/src/features/enforcement/EnforcementHistory.tsx
@@ -8,7 +8,7 @@ import { useEnforcementStore } from '@stores/enforcementStore';
import { formatDateTime } from '@shared/utils/dateFormat';
import { getViolationLabel, getViolationIntent } from '@shared/constants/violationTypes';
import { getEnforcementActionLabel } from '@shared/constants/enforcementActions';
-import { getEnforcementResultLabel, getEnforcementResultClasses } from '@shared/constants/enforcementResults';
+import { getEnforcementResultLabel, getEnforcementResultIntent } from '@shared/constants/enforcementResults';
import { useSettingsStore } from '@stores/settingsStore';
/* SFR-11: 단속 이력 관리 — 실제 백엔드 API 연동 */
@@ -107,7 +107,7 @@ export function EnforcementHistory() {
render: (v) => {
const code = v as string;
return (
-
+
{getEnforcementResultLabel(code, tc, lang)}
);
diff --git a/frontend/src/features/enforcement/EventList.tsx b/frontend/src/features/enforcement/EventList.tsx
index e3c2b88..45e9dde 100644
--- a/frontend/src/features/enforcement/EventList.tsx
+++ b/frontend/src/features/enforcement/EventList.tsx
@@ -13,7 +13,7 @@ import {
import { useEventStore } from '@stores/eventStore';
import { formatDateTime } from '@shared/utils/dateFormat';
import { type AlertLevel as AlertLevelType, getAlertLevelLabel, getAlertLevelIntent } from '@shared/constants/alertLevels';
-import { getEventStatusClasses, getEventStatusLabel } from '@shared/constants/eventStatuses';
+import { getEventStatusIntent, getEventStatusLabel } from '@shared/constants/eventStatuses';
import { getViolationLabel, getViolationIntent } from '@shared/constants/violationTypes';
import { useSettingsStore } from '@stores/settingsStore';
@@ -92,7 +92,7 @@ export function EventList() {
render: (val) => {
const s = val as string;
return (
-
+
{getEventStatusLabel(s, tc, lang)}
);
diff --git a/frontend/src/features/field-ops/AIAlert.tsx b/frontend/src/features/field-ops/AIAlert.tsx
index ef78be7..4c12489 100644
--- a/frontend/src/features/field-ops/AIAlert.tsx
+++ b/frontend/src/features/field-ops/AIAlert.tsx
@@ -82,14 +82,10 @@ const cols: DataColumn[] = [
sortable: true,
render: (v) => {
const s = v as string;
- const c =
- s === 'DELIVERED'
- ? 'bg-green-500/20 text-green-400'
- : s === 'SENT'
- ? 'bg-blue-500/20 text-blue-400'
- : 'bg-red-500/20 text-red-400';
+ const intent: 'success' | 'info' | 'critical' =
+ s === 'DELIVERED' ? 'success' : s === 'SENT' ? 'info' : 'critical';
return (
- {STATUS_LABEL[s] ?? s}
+ {STATUS_LABEL[s] ?? s}
);
},
},
diff --git a/frontend/src/features/monitoring/SystemStatusPanel.tsx b/frontend/src/features/monitoring/SystemStatusPanel.tsx
index 4cf1d69..99bc29e 100644
--- a/frontend/src/features/monitoring/SystemStatusPanel.tsx
+++ b/frontend/src/features/monitoring/SystemStatusPanel.tsx
@@ -2,6 +2,7 @@ import { useEffect, useState, useCallback } from 'react';
import { Loader2, RefreshCw, Activity, Database, Wifi } from 'lucide-react';
import { Card, CardContent } from '@shared/components/ui/card';
import { Badge } from '@shared/components/ui/badge';
+import type { BadgeIntent } from '@lib/theme/variants';
import { fetchVesselAnalysis, type VesselAnalysisStats } from '@/services/vesselAnalysisApi';
const API_BASE = import.meta.env.VITE_API_URL ?? '/api';
@@ -85,7 +86,7 @@ export function SystemStatusPanel() {
icon={}
title="KCG AI Backend"
status="UP"
- statusColor="text-green-400"
+ statusIntent="success"
details={[
['포트', ':8080'],
['프로파일', 'local'],
@@ -98,7 +99,7 @@ export function SystemStatusPanel() {
icon={}
title="iran 백엔드 (분석)"
status={stats ? 'CONNECTED' : 'DISCONNECTED'}
- statusColor={stats ? 'text-green-400' : 'text-red-400'}
+ statusIntent={stats ? 'success' : 'critical'}
details={[
['선박 분석', stats ? `${stats.total.toLocaleString()}건` : '-'],
['클러스터', stats ? `${stats.clusterCount}` : '-'],
@@ -111,7 +112,7 @@ export function SystemStatusPanel() {
icon={}
title="Prediction Service"
status={health?.status || 'UNKNOWN'}
- statusColor={health?.status === 'ok' ? 'text-green-400' : 'text-yellow-400'}
+ statusIntent={health?.status === 'ok' ? 'success' : 'warning'}
details={[
['SNPDB', health?.snpdb === true ? 'OK' : '-'],
['KCGDB', health?.kcgdb === true ? 'OK' : '-'],
@@ -134,11 +135,11 @@ export function SystemStatusPanel() {
);
}
-function ServiceCard({ icon, title, status, statusColor, details }: {
+function ServiceCard({ icon, title, status, statusIntent, details }: {
icon: React.ReactNode;
title: string;
status: string;
- statusColor: string;
+ statusIntent: BadgeIntent;
details: [string, string][];
}) {
return (
@@ -148,7 +149,7 @@ function ServiceCard({ icon, title, status, statusColor, details }: {
{icon}
{title}
-
+
{status}
diff --git a/frontend/src/features/parent-inference/ParentExclusion.tsx b/frontend/src/features/parent-inference/ParentExclusion.tsx
index 3454877..ee160b5 100644
--- a/frontend/src/features/parent-inference/ParentExclusion.tsx
+++ b/frontend/src/features/parent-inference/ParentExclusion.tsx
@@ -212,7 +212,7 @@ export function ParentExclusion() {
| {it.id} |
-
+
{it.scopeType}
|
diff --git a/frontend/src/features/patrol/FleetOptimization.tsx b/frontend/src/features/patrol/FleetOptimization.tsx
index b638423..c66e7d7 100644
--- a/frontend/src/features/patrol/FleetOptimization.tsx
+++ b/frontend/src/features/patrol/FleetOptimization.tsx
@@ -6,6 +6,7 @@ import { Badge } from '@shared/components/ui/badge';
import { Button } from '@shared/components/ui/button';
import { PageContainer, PageHeader } from '@shared/components/layout';
import { Users, Ship, Target, BarChart3, Play, CheckCircle, AlertTriangle, Layers, RefreshCw } from 'lucide-react';
+import { getStatusIntent } from '@shared/constants/statusIntent';
import { usePatrolStore } from '@stores/patrolStore';
/* SFR-08: AI 경비함정 다함정 협력형 경로 최적화 서비스 */
@@ -150,7 +151,7 @@ export function FleetOptimization() {
{f.name}
- {f.status}
+ {f.status}
구역: {f.zone}속력: {f.speed}연료: {f.fuel}%
diff --git a/frontend/src/features/patrol/PatrolRoute.tsx b/frontend/src/features/patrol/PatrolRoute.tsx
index b77a5bd..d098708 100644
--- a/frontend/src/features/patrol/PatrolRoute.tsx
+++ b/frontend/src/features/patrol/PatrolRoute.tsx
@@ -125,7 +125,7 @@ export function PatrolRoute() {
className={`px-3 py-2 rounded-lg cursor-pointer transition-colors ${selectedShip === s.id ? 'bg-cyan-600/20 border border-cyan-500/30' : 'bg-surface-overlay border border-transparent hover:border-border'} ${s.status !== '가용' ? 'opacity-40 cursor-not-allowed' : ''}`}>
{s.name}
- {s.status}
+ {s.status}
{s.class} · {s.speed} · {s.range}
diff --git a/frontend/src/features/risk-assessment/EnforcementPlan.tsx b/frontend/src/features/risk-assessment/EnforcementPlan.tsx
index 8b39e5f..3d0979b 100644
--- a/frontend/src/features/risk-assessment/EnforcementPlan.tsx
+++ b/frontend/src/features/risk-assessment/EnforcementPlan.tsx
@@ -5,6 +5,7 @@ import { Badge } from '@shared/components/ui/badge';
import { Button } from '@shared/components/ui/button';
import { PageContainer, PageHeader } from '@shared/components/layout';
import { DataTable, type DataColumn } from '@shared/components/common/DataTable';
+import { getRiskIntent, getStatusIntent } from '@shared/constants/statusIntent';
import { Shield, AlertTriangle, Ship, Plus, Calendar, Users } from 'lucide-react';
import { BaseMap, STATIC_LAYERS, createMarkerLayer, createRadiusLayer, useMapLayers, type MapHandle } from '@lib/map';
import type { MarkerData } from '@lib/map';
@@ -34,12 +35,12 @@ const cols: DataColumn[] = [
{ key: 'id', label: 'ID', width: '70px', render: v => {v as string} },
{ key: 'zone', label: '단속 구역', sortable: true, render: v => {v as string} },
{ key: 'risk', label: '위험도', width: '70px', align: 'center', sortable: true,
- render: v => { const n = v as number; return 80 ? 'bg-red-500/20 text-red-400' : n > 60 ? 'bg-orange-500/20 text-orange-400' : 'bg-yellow-500/20 text-yellow-400'}`}>{n}점; } },
+ render: v => { const n = v as number; return {n}점; } },
{ key: 'period', label: '단속 시간', width: '160px', render: v => {v as string} },
{ key: 'ships', label: '참여 함정', render: v => {v as string} },
{ key: 'crew', label: '인력', width: '50px', align: 'right', render: v => {v as number || '-'} },
{ key: 'status', label: '상태', width: '70px', align: 'center', sortable: true,
- render: v => { const s = v as string; return {s}; } },
+ render: v => { const s = v as string; return {s}; } },
{ key: 'alert', label: '경보', width: '80px', align: 'center',
render: v => { const a = v as string; return a === '경보 발령' || a === 'ALERT' ? {a} : {a}; } },
];
diff --git a/frontend/src/features/statistics/ExternalService.tsx b/frontend/src/features/statistics/ExternalService.tsx
index c53401f..776ad2c 100644
--- a/frontend/src/features/statistics/ExternalService.tsx
+++ b/frontend/src/features/statistics/ExternalService.tsx
@@ -2,6 +2,8 @@ import { useTranslation } from 'react-i18next';
import { Badge } from '@shared/components/ui/badge';
import { PageContainer, PageHeader } from '@shared/components/layout';
import { DataTable, type DataColumn } from '@shared/components/common/DataTable';
+import { getStatusIntent } from '@shared/constants/statusIntent';
+import type { BadgeIntent } from '@lib/theme/variants';
import { Globe } from 'lucide-react';
/* SFR-14: 외부 서비스(예보·경보) 제공 결과 연계 */
@@ -22,9 +24,13 @@ const cols: DataColumn[] = [
{ key: 'format', label: '포맷', width: '60px', align: 'center' },
{ key: 'cycle', label: '갱신주기', width: '70px' },
{ key: 'privacy', label: '정보등급', width: '70px', align: 'center',
- render: v => { const p = v as string; const c = p === '비공개' ? 'bg-red-500/20 text-red-400' : p === '비식별' ? 'bg-yellow-500/20 text-yellow-400' : p === '익명화' ? 'bg-blue-500/20 text-blue-400' : 'bg-green-500/20 text-green-400'; return {p}; } },
+ render: v => {
+ const p = v as string;
+ const intent: BadgeIntent = p === '비공개' ? 'critical' : p === '비식별' ? 'warning' : p === '익명화' ? 'info' : 'success';
+ return {p};
+ } },
{ key: 'status', label: '상태', width: '60px', align: 'center', sortable: true,
- render: v => { const s = v as string; const c = s === '운영' ? 'bg-green-500/20 text-green-400' : s === '테스트' ? 'bg-blue-500/20 text-blue-400' : 'bg-muted text-muted-foreground'; return {s}; } },
+ render: v => { const s = v as string; return {s}; } },
{ key: 'calls', label: '호출 수', width: '70px', align: 'right', render: v => {v as string} },
];
diff --git a/frontend/src/features/statistics/ReportManagement.tsx b/frontend/src/features/statistics/ReportManagement.tsx
index 5934b1f..b1ea820 100644
--- a/frontend/src/features/statistics/ReportManagement.tsx
+++ b/frontend/src/features/statistics/ReportManagement.tsx
@@ -11,21 +11,23 @@ import { SaveButton } from '@shared/components/common/SaveButton';
import { SearchInput } from '@shared/components/common/SearchInput';
import { FileText, Plus, Upload, X, Clock, MapPin, Download } from 'lucide-react';
+import type { BadgeIntent } from '@lib/theme/variants';
+
interface Report {
id: string;
name: string;
type: string;
status: string;
- statusColor: string;
+ statusIntent: BadgeIntent;
date: string;
mmsiNote: string;
evidence: number;
}
const reports: Report[] = [
- { id: 'RPT-2024-0142', name: '浙江렌센號', type: 'EEZ 침범', status: 'EEZ', statusColor: 'bg-green-500', date: '2026-01-20 14:30:00', mmsiNote: 'MMSI 변조', evidence: 12 },
- { id: 'RPT-2024-0231', name: '福建海丰號', type: 'EEZ 침범', status: '확인', statusColor: 'bg-blue-500', date: '2026-01-20 14:29:00', mmsiNote: '', evidence: 8 },
- { id: 'RPT-2024-0089', name: '무명선박-A', type: '다크베셀', status: '처리중', statusColor: 'bg-yellow-500', date: '2026-01-20 14:05:00', mmsiNote: '', evidence: 6 },
+ { id: 'RPT-2024-0142', name: '浙江렌센號', type: 'EEZ 침범', status: 'EEZ', statusIntent: 'success', date: '2026-01-20 14:30:00', mmsiNote: 'MMSI 변조', evidence: 12 },
+ { id: 'RPT-2024-0231', name: '福建海丰號', type: 'EEZ 침범', status: '확인', statusIntent: 'info', date: '2026-01-20 14:29:00', mmsiNote: '', evidence: 8 },
+ { id: 'RPT-2024-0089', name: '무명선박-A', type: '다크베셀', status: '처리중', statusIntent: 'warning', date: '2026-01-20 14:05:00', mmsiNote: '', evidence: 6 },
];
export function ReportManagement() {
@@ -106,7 +108,7 @@ export function ReportManagement() {
>
{r.name}
- {r.status}
+ {r.status}
{r.id}
diff --git a/frontend/src/features/surveillance/MapControl.tsx b/frontend/src/features/surveillance/MapControl.tsx
index e3f9ec6..90d07e9 100644
--- a/frontend/src/features/surveillance/MapControl.tsx
+++ b/frontend/src/features/surveillance/MapControl.tsx
@@ -6,6 +6,7 @@ import { PageContainer, PageHeader } from '@shared/components/layout';
import { DataTable, type DataColumn } from '@shared/components/common/DataTable';
import { Map, Shield, Crosshair, AlertTriangle, Eye, Anchor, Ship, Filter, Layers, Target, Clock, MapPin, Bell, Navigation, Info } from 'lucide-react';
import { getTrainingZoneIntent, getTrainingZoneHex, getTrainingZoneMeta } from '@shared/constants/trainingZoneTypes';
+import type { BadgeIntent } from '@lib/theme/variants';
/*
* 해역 통제 — 한국연안 해상사격 훈련구역도 (No.462) 반영
@@ -128,18 +129,18 @@ const ntmColumns: DataColumn [] = [
{ key: 'category', label: '구분', width: '70px', align: 'center', sortable: true,
render: v => {
const c = v as string;
- const color = c.includes('사격') || c.includes('군사') ? 'bg-red-500/20 text-red-400'
- : c.includes('기뢰') ? 'bg-orange-500/20 text-orange-400'
- : c.includes('오염') ? 'bg-yellow-500/20 text-yellow-400'
- : 'bg-blue-500/20 text-blue-400';
- return {c};
+ const intent: BadgeIntent = c.includes('사격') || c.includes('군사') ? 'critical'
+ : c.includes('기뢰') ? 'high'
+ : c.includes('오염') ? 'warning'
+ : 'info';
+ return {c};
},
},
{ key: 'sea', label: '해역', width: '50px', sortable: true },
{ key: 'title', label: '제목', sortable: true, render: v => {v as string} },
{ key: 'position', label: '위치', width: '120px', render: v => {v as string} },
{ key: 'status', label: '상태', width: '70px', align: 'center', sortable: true,
- render: v => {v as string} },
+ render: v => {v as string} },
];
// 훈련구역 색상은 trainingZoneTypes 카탈로그에서 lookup
@@ -153,7 +154,7 @@ const columns: DataColumn[] = [
{ key: 'lng', label: '경도', width: '110px', render: v => {v as string} },
{ key: 'radius', label: '반경', width: '60px', align: 'center' },
{ key: 'status', label: '상태', width: '60px', align: 'center', sortable: true,
- render: v => {v as string} },
+ render: v => {v as string} },
{ key: 'schedule', label: '운용', width: '60px', align: 'center' },
{ key: 'note', label: '비고', render: v => {v as string} },
];
diff --git a/frontend/src/shared/constants/enforcementActions.ts b/frontend/src/shared/constants/enforcementActions.ts
index e9249af..f791e67 100644
--- a/frontend/src/shared/constants/enforcementActions.ts
+++ b/frontend/src/shared/constants/enforcementActions.ts
@@ -7,6 +7,8 @@
* 사용처: EnforcementHistory(조치 컬럼), 단속 등록 폼
*/
+import type { BadgeIntent } from '@lib/theme/variants';
+
export type EnforcementAction =
| 'CAPTURE'
| 'INSPECT'
@@ -19,6 +21,7 @@ export interface EnforcementActionMeta {
code: EnforcementAction;
i18nKey: string;
fallback: { ko: string; en: string };
+ intent: BadgeIntent;
classes: string;
hex: string;
order: number;
@@ -29,6 +32,7 @@ export const ENFORCEMENT_ACTIONS: Record = {
code: 'NEW',
i18nKey: 'eventStatus.NEW',
fallback: { ko: '신규', en: 'New' },
+ intent: 'critical',
classes: 'bg-red-100 text-red-800 dark:bg-red-500/20 dark:text-red-400',
order: 1,
},
@@ -34,6 +38,7 @@ export const EVENT_STATUSES: Record = {
code: 'ACK',
i18nKey: 'eventStatus.ACK',
fallback: { ko: '확인', en: 'Acknowledged' },
+ intent: 'high',
classes: 'bg-orange-100 text-orange-800 dark:bg-orange-500/20 dark:text-orange-400',
order: 2,
},
@@ -41,6 +46,7 @@ export const EVENT_STATUSES: Record = {
code: 'IN_PROGRESS',
i18nKey: 'eventStatus.IN_PROGRESS',
fallback: { ko: '처리중', en: 'In Progress' },
+ intent: 'info',
classes: 'bg-blue-100 text-blue-800 dark:bg-blue-500/20 dark:text-blue-400',
order: 3,
},
@@ -48,6 +54,7 @@ export const EVENT_STATUSES: Record = {
code: 'RESOLVED',
i18nKey: 'eventStatus.RESOLVED',
fallback: { ko: '완료', en: 'Resolved' },
+ intent: 'success',
classes: 'bg-green-100 text-green-800 dark:bg-green-500/20 dark:text-green-400',
order: 4,
},
@@ -55,11 +62,16 @@ export const EVENT_STATUSES: Record = {
code: 'FALSE_POSITIVE',
i18nKey: 'eventStatus.FALSE_POSITIVE',
fallback: { ko: '오탐', en: 'False Positive' },
+ intent: 'muted',
classes: 'bg-muted text-muted-foreground',
order: 5,
},
};
+export function getEventStatusIntent(status: string): BadgeIntent {
+ return getEventStatusMeta(status)?.intent ?? 'muted';
+}
+
export function getEventStatusMeta(status: string): EventStatusMeta | undefined {
return EVENT_STATUSES[status as EventStatus];
}
diff --git a/frontend/src/shared/constants/index.ts b/frontend/src/shared/constants/index.ts
index 6d395b6..112a84f 100644
--- a/frontend/src/shared/constants/index.ts
+++ b/frontend/src/shared/constants/index.ts
@@ -29,3 +29,4 @@ export * from './vesselAnalysisStatuses';
export * from './connectionStatuses';
export * from './trainingZoneTypes';
export * from './kpiUiMap';
+export * from './statusIntent';
diff --git a/frontend/src/shared/constants/patrolStatuses.ts b/frontend/src/shared/constants/patrolStatuses.ts
index 721cd9d..d1bd898 100644
--- a/frontend/src/shared/constants/patrolStatuses.ts
+++ b/frontend/src/shared/constants/patrolStatuses.ts
@@ -7,6 +7,8 @@
* 사용처: Dashboard PatrolStatusBadge, ShipAgent
*/
+import type { BadgeIntent } from '@lib/theme/variants';
+
export type PatrolStatus =
| 'AVAILABLE'
| 'ON_PATROL'
@@ -20,6 +22,7 @@ export interface PatrolStatusMeta {
code: PatrolStatus;
i18nKey: string;
fallback: { ko: string; en: string };
+ intent: BadgeIntent;
classes: string;
order: number;
}
@@ -29,6 +32,7 @@ export const PATROL_STATUSES: Record = {
code: 'IN_PURSUIT',
i18nKey: 'patrolStatus.IN_PURSUIT',
fallback: { ko: '추적중', en: 'In Pursuit' },
+ intent: 'critical',
classes:
'bg-red-100 text-red-800 border-red-300 dark:bg-red-500/20 dark:text-red-400 dark:border-red-500/30',
order: 1,
@@ -37,6 +41,7 @@ export const PATROL_STATUSES: Record = {
code: 'INSPECTING',
i18nKey: 'patrolStatus.INSPECTING',
fallback: { ko: '검문중', en: 'Inspecting' },
+ intent: 'high',
classes:
'bg-orange-100 text-orange-800 border-orange-300 dark:bg-orange-500/20 dark:text-orange-400 dark:border-orange-500/30',
order: 2,
@@ -45,6 +50,7 @@ export const PATROL_STATUSES: Record = {
code: 'ON_PATROL',
i18nKey: 'patrolStatus.ON_PATROL',
fallback: { ko: '초계중', en: 'On Patrol' },
+ intent: 'info',
classes:
'bg-blue-100 text-blue-800 border-blue-300 dark:bg-blue-500/20 dark:text-blue-400 dark:border-blue-500/30',
order: 3,
@@ -53,6 +59,7 @@ export const PATROL_STATUSES: Record = {
code: 'RETURNING',
i18nKey: 'patrolStatus.RETURNING',
fallback: { ko: '귀항중', en: 'Returning' },
+ intent: 'purple',
classes:
'bg-purple-100 text-purple-800 border-purple-300 dark:bg-purple-500/20 dark:text-purple-400 dark:border-purple-500/30',
order: 4,
@@ -61,6 +68,7 @@ export const PATROL_STATUSES: Record = {
code: 'AVAILABLE',
i18nKey: 'patrolStatus.AVAILABLE',
fallback: { ko: '가용', en: 'Available' },
+ intent: 'success',
classes:
'bg-green-100 text-green-800 border-green-300 dark:bg-green-500/20 dark:text-green-400 dark:border-green-500/30',
order: 5,
@@ -69,6 +77,7 @@ export const PATROL_STATUSES: Record = {
code: 'STANDBY',
i18nKey: 'patrolStatus.STANDBY',
fallback: { ko: '대기', en: 'Standby' },
+ intent: 'muted',
classes:
'bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-500/20 dark:text-slate-400 dark:border-slate-500/30',
order: 6,
@@ -77,12 +86,17 @@ export const PATROL_STATUSES: Record = {
code: 'MAINTENANCE',
i18nKey: 'patrolStatus.MAINTENANCE',
fallback: { ko: '정비중', en: 'Maintenance' },
+ intent: 'warning',
classes:
'bg-yellow-100 text-yellow-800 border-yellow-300 dark:bg-yellow-500/20 dark:text-yellow-400 dark:border-yellow-500/30',
order: 7,
},
};
+export function getPatrolStatusIntent(status: string): BadgeIntent {
+ return getPatrolStatusMeta(status)?.intent ?? 'muted';
+}
+
/** 한글 라벨도 키로 받아주는 호환성 매핑 (mock 데이터에서 한글 사용 중) */
const LEGACY_KO_LABELS: Record = {
'추적 중': 'IN_PURSUIT',
diff --git a/frontend/src/shared/constants/statusIntent.ts b/frontend/src/shared/constants/statusIntent.ts
new file mode 100644
index 0000000..0ec87f6
--- /dev/null
+++ b/frontend/src/shared/constants/statusIntent.ts
@@ -0,0 +1,163 @@
+/**
+ * 일반 상태 문자열 → BadgeIntent 매핑 유틸
+ *
+ * 정식 카탈로그에 없는 ad-hoc 상태 문자열(한글/영문 섞여 있는 mock 데이터 등)을
+ * 임시로 intent에 매핑. 프로젝트 전역에서 재사용 가능.
+ *
+ * 원칙:
+ * - 가능하면 전용 카탈로그를 만들어 사용하는 것이 우선
+ * - 이 유틸은 정형화되지 않은 데모/mock 데이터 대응 임시 매핑용
+ *
+ * 사용:
+ * {s}
+ */
+
+import type { BadgeIntent } from '@lib/theme/variants';
+
+const STATUS_INTENT_MAP: Record = {
+ // 정상/긍정
+ '정상': 'success',
+ '운영': 'success',
+ '운영중': 'success',
+ '활성': 'success',
+ '완료': 'success',
+ '확정': 'success',
+ '가용': 'success',
+ '승인': 'success',
+ '성공': 'success',
+ '통과': 'success',
+ '배포': 'success',
+ active: 'success',
+ running: 'info',
+ online: 'success',
+ healthy: 'success',
+ success: 'success',
+ ok: 'success',
+ passed: 'success',
+ deployed: 'success',
+ confirmed: 'success',
+ CONFIRMED: 'success',
+ ACTIVE: 'success',
+ PASSED: 'success',
+ RUNNING: 'info',
+ DEPLOYED: 'success',
+
+ // 정보/대기
+ '대기': 'info',
+ '계획': 'info',
+ '계획중': 'info',
+ '진행': 'info',
+ '진행중': 'info',
+ '처리': 'info',
+ '처리중': 'info',
+ '생성': 'info',
+ '예약': 'info',
+ '예정': 'info',
+ '테스트': 'info',
+ pending: 'info',
+ PENDING: 'info',
+ planning: 'info',
+ PLANNING: 'info',
+ PLANNED: 'info',
+ scheduled: 'info',
+ testing: 'info',
+ TESTING: 'info',
+ STAGING: 'info',
+ CANARY: 'info',
+
+ // 주의
+ '주의': 'warning',
+ '경고': 'warning',
+ '검토': 'warning',
+ '검토필요': 'warning',
+ '수정': 'warning',
+ '변경': 'warning',
+ '점검': 'warning',
+ warning: 'warning',
+ WARNING: 'warning',
+ review: 'warning',
+ reviewing: 'warning',
+ maintenance: 'warning',
+
+ // 심각/에러
+ '긴급': 'critical',
+ '오류': 'critical',
+ '실패': 'critical',
+ '에러': 'critical',
+ '차단': 'critical',
+ '정지': 'critical',
+ '거부': 'critical',
+ '폐기': 'critical',
+ '만료': 'critical',
+ critical: 'critical',
+ CRITICAL: 'critical',
+ error: 'critical',
+ ERROR: 'critical',
+ failed: 'critical',
+ FAILED: 'critical',
+ blocked: 'critical',
+ rejected: 'critical',
+ REJECTED: 'critical',
+ expired: 'critical',
+ EXPIRED: 'critical',
+
+ // 높음
+ '높음': 'high',
+ '높은': 'high',
+ high: 'high',
+ HIGH: 'high',
+
+ // 보라/특수
+ '분석': 'purple',
+ '학습': 'purple',
+ '추론': 'purple',
+ '배포중': 'purple',
+ analyzing: 'purple',
+ training: 'purple',
+
+ // 청록/모니터링
+ '모니터': 'cyan',
+ '모니터링': 'cyan',
+ '감시': 'cyan',
+ '추적': 'cyan',
+ '추적중': 'cyan',
+ monitoring: 'cyan',
+ tracking: 'cyan',
+
+ // 비활성/중립
+ '비활성': 'muted',
+ '미배포': 'muted',
+ '없음': 'muted',
+ '-': 'muted',
+ '기타': 'muted',
+ '오탐': 'muted',
+ inactive: 'muted',
+ INACTIVE: 'muted',
+ disabled: 'muted',
+ DISABLED: 'muted',
+ none: 'muted',
+ unknown: 'muted',
+ UNKNOWN: 'muted',
+ ARCHIVED: 'muted',
+ DEV: 'muted',
+};
+
+/**
+ * 상태 문자열을 BadgeIntent로 변환.
+ * 매핑 없으면 'muted' 반환.
+ */
+export function getStatusIntent(status: string | null | undefined): BadgeIntent {
+ if (!status) return 'muted';
+ return STATUS_INTENT_MAP[status] ?? STATUS_INTENT_MAP[status.toLowerCase()] ?? 'muted';
+}
+
+/**
+ * 숫자 위험도(0-100)를 intent로 매핑.
+ * 80 이상 critical, 60 이상 high, 40 이상 warning, 아니면 info
+ */
+export function getRiskIntent(score: number): BadgeIntent {
+ if (score >= 80) return 'critical';
+ if (score >= 60) return 'high';
+ if (score >= 40) return 'warning';
+ return 'info';
+}
|