diff --git a/frontend/src/design-system/sections/BadgeSection.tsx b/frontend/src/design-system/sections/BadgeSection.tsx index 2af48bc..7a5a07c 100644 --- a/frontend/src/design-system/sections/BadgeSection.tsx +++ b/frontend/src/design-system/sections/BadgeSection.tsx @@ -1,20 +1,10 @@ import { TrkSectionHeader, Trk } from '../lib/Trk'; import { Badge } from '@shared/components/ui/badge'; -import type { BadgeIntent, BadgeSize } from '@lib/theme/variants'; - -const INTENTS: BadgeIntent[] = ['critical', 'high', 'warning', 'info', 'success', 'muted', 'purple', 'cyan']; -const SIZES: BadgeSize[] = ['xs', 'sm', 'md', 'lg']; - -const INTENT_USAGE: Record = { - critical: '심각 · 긴급 · 위험 (빨강 계열)', - high: '높음 · 경고 (주황 계열)', - warning: '주의 · 보류 (노랑 계열)', - info: '일반 · 정보 (파랑 계열, 기본값)', - success: '성공 · 완료 · 정상 (초록 계열)', - muted: '비활성 · 중립 · 기타 (회색 계열)', - purple: '분석 · AI · 특수 (보라 계열)', - cyan: '모니터링 · 스트림 (청록 계열)', -}; +import { + BADGE_INTENT_META, + BADGE_INTENT_ORDER, + BADGE_SIZE_ORDER, +} from '@lib/theme/variantMeta'; export function BadgeSection() { return ( @@ -22,18 +12,20 @@ export function BadgeSection() { - {/* 32 변형 그리드 */} -

32 변형 매트릭스

+ {/* 변형 그리드 */} +

변형 매트릭스

- - {SIZES.map((size) => ( + + {BADGE_SIZE_ORDER.map((size) => ( @@ -41,10 +33,10 @@ export function BadgeSection() { - {INTENTS.map((intent) => ( + {BADGE_INTENT_ORDER.map((intent) => ( - {SIZES.map((size) => ( + {BADGE_SIZE_ORDER.map((size) => (
intent ↓ / size → + intent ↓ / size → + {size}
{intent} @@ -60,19 +52,22 @@ export function BadgeSection() { - {/* intent별 의미 가이드 */} + {/* intent 의미 가이드 (variantMeta에서 자동 열거) */}

Intent 의미 가이드

- {INTENTS.map((intent) => ( - -
- - {intent.toUpperCase()} - - {INTENT_USAGE[intent]} -
-
- ))} + {BADGE_INTENT_ORDER.map((intent) => { + const meta = BADGE_INTENT_META[intent]; + return ( + +
+ + {meta.titleKo} + + {meta.description} +
+
+ ); + })}
{/* 사용 예시 코드 */} @@ -82,7 +77,7 @@ export function BadgeSection() { {`import { Badge } from '@shared/components/ui/badge'; import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels'; -// 카탈로그 API와 결합 +// 카탈로그 API와 결합 — 라벨/색상 변경은 카탈로그 파일에서만 {getAlertLevelLabel('CRITICAL', t, lang)} diff --git a/frontend/src/design-system/sections/ButtonSection.tsx b/frontend/src/design-system/sections/ButtonSection.tsx index 21233bf..0987990 100644 --- a/frontend/src/design-system/sections/ButtonSection.tsx +++ b/frontend/src/design-system/sections/ButtonSection.tsx @@ -1,37 +1,32 @@ import { TrkSectionHeader, Trk } from '../lib/Trk'; import { Button } from '@shared/components/ui/button'; -import type { ButtonVariant, ButtonSize } from '@lib/theme/variants'; +import { + BUTTON_VARIANT_META, + BUTTON_VARIANT_ORDER, + BUTTON_SIZE_ORDER, +} from '@lib/theme/variantMeta'; import { Plus, Download, Trash2, Search, Save } from 'lucide-react'; -const VARIANTS: ButtonVariant[] = ['primary', 'secondary', 'ghost', 'outline', 'destructive']; -const SIZES: ButtonSize[] = ['sm', 'md', 'lg']; - -const VARIANT_USAGE: Record = { - primary: '주요 액션 · 기본 CTA (페이지당 1개 권장)', - secondary: '보조 액션 · 툴바 버튼 (기본값)', - ghost: '고요한 액션 · 리스트 행 내부', - outline: '강조 보조 · 필터 활성화 상태', - destructive: '삭제 · 비활성화 등 위험 액션', -}; - export function ButtonSection() { return ( <> {/* 매트릭스 */} -

15 변형 매트릭스

+

변형 매트릭스

- - {SIZES.map((size) => ( + + {BUTTON_SIZE_ORDER.map((size) => ( @@ -39,10 +34,10 @@ export function ButtonSection() { - {VARIANTS.map((variant) => ( + {BUTTON_VARIANT_ORDER.map((variant) => ( - {SIZES.map((size) => ( + {BUTTON_SIZE_ORDER.map((size) => (
variant ↓ / size → + variant ↓ / size → + {size}
{variant} - {VARIANT_USAGE[variant]} - - - ))} + {BUTTON_VARIANT_ORDER.map((variant) => { + const meta = BUTTON_VARIANT_META[variant]; + return ( + +
+ + {meta.description} +
+
+ ); + })} {/* 사용 예시 */} diff --git a/frontend/src/design-system/sections/CatalogSection.tsx b/frontend/src/design-system/sections/CatalogSection.tsx index 9ea946a..e18a475 100644 --- a/frontend/src/design-system/sections/CatalogSection.tsx +++ b/frontend/src/design-system/sections/CatalogSection.tsx @@ -2,37 +2,14 @@ import { type ReactNode } from 'react'; import { TrkSectionHeader, Trk } from '../lib/Trk'; import { Badge } from '@shared/components/ui/badge'; import type { BadgeIntent } from '@lib/theme/variants'; +import { CATALOG_REGISTRY, type CatalogEntry } from '@shared/constants/catalogRegistry'; -import { ALERT_LEVELS } from '@shared/constants/alertLevels'; -import { VIOLATION_TYPES } from '@shared/constants/violationTypes'; -import { EVENT_STATUSES } from '@shared/constants/eventStatuses'; -import { ENFORCEMENT_ACTIONS } from '@shared/constants/enforcementActions'; -import { ENFORCEMENT_RESULTS } from '@shared/constants/enforcementResults'; -import { PATROL_STATUSES } from '@shared/constants/patrolStatuses'; -import { ENGINE_SEVERITIES } from '@shared/constants/engineSeverities'; -import { DEVICE_STATUSES } from '@shared/constants/deviceStatuses'; -import { - PARENT_RESOLUTION_STATUSES, - LABEL_SESSION_STATUSES, -} from '@shared/constants/parentResolutionStatuses'; -import { - MODEL_STATUSES, - QUALITY_GATE_STATUSES, - EXPERIMENT_STATUSES, -} from '@shared/constants/modelDeploymentStatuses'; -import { GEAR_GROUP_TYPES } from '@shared/constants/gearGroupTypes'; -import { DARK_VESSEL_PATTERNS } from '@shared/constants/darkVesselPatterns'; -import { USER_ACCOUNT_STATUSES } from '@shared/constants/userAccountStatuses'; -import { LOGIN_RESULTS } from '@shared/constants/loginResultStatuses'; -import { PERMIT_STATUSES, GEAR_JUDGMENTS } from '@shared/constants/permissionStatuses'; -import { - VESSEL_SURVEILLANCE_STATUSES, - VESSEL_RISK_RINGS, -} from '@shared/constants/vesselAnalysisStatuses'; -import { CONNECTION_STATUSES } from '@shared/constants/connectionStatuses'; -import { TRAINING_ZONE_TYPES } from '@shared/constants/trainingZoneTypes'; +/** + * 카탈로그 섹션 — `CATALOG_REGISTRY`를 자동 열거. + * 새 카탈로그 추가는 `catalogRegistry.ts`에 한 줄 추가하면 끝. + * 여기는 렌더링 로직만 담당. + */ -/** 카탈로그 메타 공통 속성 (일부만 있을 수 있음) */ interface AnyMeta { code: string; intent?: BadgeIntent; @@ -41,8 +18,6 @@ interface AnyMeta { label?: string; } -type AnyCatalog = Record; - function getKoLabel(meta: AnyMeta): string { return meta.fallback?.ko ?? meta.label ?? meta.code; } @@ -51,7 +26,6 @@ function getEnLabel(meta: AnyMeta): string | undefined { return meta.fallback?.en; } -/** classes가 문자열인 경우 그대로, 객체인 경우 bg+text 조합 */ function getFallbackClasses(meta: AnyMeta): string | undefined { if (typeof meta.classes === 'string') return meta.classes; if (typeof meta.classes === 'object' && meta.classes) { @@ -60,7 +34,6 @@ function getFallbackClasses(meta: AnyMeta): string | undefined { return undefined; } -/** 단일 배지 렌더 — Badge(intent) 또는 span(classes) */ function renderBadge(meta: AnyMeta, label: string): ReactNode { if (meta.intent) { return ( @@ -81,15 +54,14 @@ function renderBadge(meta: AnyMeta, label: string): ReactNode { ); } -/** 카탈로그 항목을 행 단위로 렌더: [code] [한글 배지] [영문 배지] */ -function CatalogBadges({ catalog, idPrefix }: { catalog: AnyCatalog; idPrefix: string }) { - const entries = Object.values(catalog); +function CatalogBadges({ entry }: { entry: CatalogEntry }) { + const items = Object.values(entry.items) as AnyMeta[]; return (
- {entries.map((meta) => { + {items.map((meta) => { const koLabel = getKoLabel(meta); const enLabel = getEnLabel(meta); - const trkId = `${idPrefix}-${meta.code}`; + const trkId = `${entry.showcaseId}-${meta.code}`; return ( @@ -110,169 +82,18 @@ function CatalogBadges({ catalog, idPrefix }: { catalog: AnyCatalog; idPrefix: s ); } -interface CatalogEntry { - id: string; - title: string; - description: string; - catalog: AnyCatalog; - source?: string; -} - -const CATALOGS: CatalogEntry[] = [ - { - id: 'TRK-CAT-alert-level', - title: '위험도 · AlertLevel', - description: 'CRITICAL / HIGH / MEDIUM / LOW — 모든 이벤트/알림 배지', - catalog: ALERT_LEVELS as unknown as AnyCatalog, - source: 'backend code_master EVENT_LEVEL', - }, - { - id: 'TRK-CAT-violation-type', - title: '위반 유형 · ViolationType', - description: '중국불법조업 / 환적의심 / EEZ침범 등', - catalog: VIOLATION_TYPES as unknown as AnyCatalog, - source: 'backend ViolationType enum', - }, - { - id: 'TRK-CAT-event-status', - title: '이벤트 상태 · EventStatus', - description: 'NEW / ACK / IN_PROGRESS / RESOLVED / FALSE_POSITIVE', - catalog: EVENT_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-enforcement-action', - title: '단속 조치 · EnforcementAction', - description: 'CAPTURE / INSPECT / WARN / DISPERSE / TRACK / EVIDENCE', - catalog: ENFORCEMENT_ACTIONS as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-enforcement-result', - title: '단속 결과 · EnforcementResult', - description: 'PUNISHED / REFERRED / WARNED / RELEASED / FALSE_POSITIVE', - catalog: ENFORCEMENT_RESULTS as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-patrol-status', - title: '함정 상태 · PatrolStatus', - description: '출동 / 순찰 / 복귀 / 정박 / 정비 / 대기', - catalog: PATROL_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-engine-severity', - title: '엔진 심각도 · EngineSeverity', - description: 'AI 모델/분석엔진 오류 심각도', - catalog: ENGINE_SEVERITIES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-device-status', - title: '함정 Agent 장치 상태 · DeviceStatus', - description: 'ONLINE / OFFLINE / SYNCING / NOT_DEPLOYED', - catalog: DEVICE_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-parent-resolution', - title: '모선 확정 상태 · ParentResolutionStatus', - description: 'PENDING / CONFIRMED / REJECTED / REVIEWING', - catalog: PARENT_RESOLUTION_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-label-session', - title: '라벨 세션 · LabelSessionStatus', - description: '모선 학습 세션 상태', - catalog: LABEL_SESSION_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-model-status', - title: 'AI 모델 상태 · ModelStatus', - description: 'DEV / STAGING / CANARY / PROD / ARCHIVED', - catalog: MODEL_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-quality-gate', - title: '품질 게이트 · QualityGateStatus', - description: '모델 배포 전 품질 검증', - catalog: QUALITY_GATE_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-experiment', - title: 'ML 실험 · ExperimentStatus', - description: 'MLOps 실험 상태', - catalog: EXPERIMENT_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-gear-group', - title: '어구 그룹 유형 · GearGroupType', - description: 'FLEET / GEAR_IN_ZONE / GEAR_OUT_ZONE', - catalog: GEAR_GROUP_TYPES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-dark-vessel', - title: '다크베셀 패턴 · DarkVesselPattern', - description: 'AIS 끊김/스푸핑 패턴 5종', - catalog: DARK_VESSEL_PATTERNS as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-user-account', - title: '사용자 계정 상태 · UserAccountStatus', - description: 'ACTIVE / LOCKED / INACTIVE / PENDING', - catalog: USER_ACCOUNT_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-login-result', - title: '로그인 결과 · LoginResult', - description: 'SUCCESS / FAILED / LOCKED', - catalog: LOGIN_RESULTS as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-permit-status', - title: '허가 상태 · PermitStatus', - description: '선박 허가 유효/만료/정지', - catalog: PERMIT_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-gear-judgment', - title: '어구 판정 · GearJudgment', - description: '합법 / 의심 / 불법', - catalog: GEAR_JUDGMENTS as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-vessel-surveillance', - title: '선박 감시 상태 · VesselSurveillanceStatus', - description: '관심선박 추적 상태', - catalog: VESSEL_SURVEILLANCE_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-vessel-risk-ring', - title: '선박 위험 링 · VesselRiskRing', - description: '지도 마커 위험도 링', - catalog: VESSEL_RISK_RINGS as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-connection', - title: '연결 상태 · ConnectionStatus', - description: 'OK / WARNING / ERROR', - catalog: CONNECTION_STATUSES as unknown as AnyCatalog, - }, - { - id: 'TRK-CAT-training-zone', - title: '훈련 수역 · TrainingZoneType', - description: 'NAVY / AIRFORCE / ARMY / ADD / KCG', - catalog: TRAINING_ZONE_TYPES as unknown as AnyCatalog, - }, -]; - export function CatalogSection() { return ( <>

- 페이지에서 배지를 렌더할 때는 반드시 카탈로그 API를 사용한다: + 페이지에서 배지를 렌더할 때는 반드시 카탈로그 API를 사용한다 (라벨/intent/색상 단일 관리):

{`import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels'; @@ -281,21 +102,27 @@ export function CatalogSection() { {getAlertLevelLabel(event.level, t, lang)} `} +

+ 새 카탈로그 추가: shared/constants/catalogRegistry.ts에 + 항목을 추가하면 이 쇼케이스에 자동 노출됩니다. +

- {CATALOGS.map((entry) => ( - + {CATALOG_REGISTRY.map((entry) => ( +
-
-

{entry.title}

- {entry.id} +
+

+ {entry.titleKo} · {entry.titleEn} +

+ {entry.showcaseId}

{entry.description}

{entry.source && (

출처: {entry.source}

)}
- + ))} diff --git a/frontend/src/lib/theme/variantMeta.ts b/frontend/src/lib/theme/variantMeta.ts new file mode 100644 index 0000000..1e63b67 --- /dev/null +++ b/frontend/src/lib/theme/variantMeta.ts @@ -0,0 +1,128 @@ +/** + * 컴포넌트 variant 메타데이터 (Single Source of Truth) + * + * variants.ts는 CVA 클래스 문자열만 정의. + * 여기서는 각 variant의 **의미/사용처/설명**을 중앙 관리하여 + * 쇼케이스와 실 페이지가 일관되게 참조 가능. + * + * 예) 쇼케이스 BadgeSection의 "intent 의미 가이드"가 이 파일을 참조. + * 코드 리뷰 시 variant 선택 기준이 이 파일에서 단일하게 유지됨. + */ + +import type { BadgeIntent, BadgeSize, ButtonVariant, ButtonSize } from './variants'; + +export interface VariantMeta { + key: K; + titleKo: string; + titleEn: string; + description: string; +} + +// ── Badge intent 의미 ────────────────────────── +export const BADGE_INTENT_META: Record> = { + critical: { + key: 'critical', + titleKo: '심각', + titleEn: 'Critical', + description: '심각 · 긴급 · 위험 (빨강 계열)', + }, + high: { + key: 'high', + titleKo: '높음', + titleEn: 'High', + description: '높음 · 경고 (주황 계열)', + }, + warning: { + key: 'warning', + titleKo: '주의', + titleEn: 'Warning', + description: '주의 · 보류 (노랑 계열)', + }, + info: { + key: 'info', + titleKo: '정보', + titleEn: 'Info', + description: '일반 · 정보 (파랑 계열, 기본값)', + }, + success: { + key: 'success', + titleKo: '성공', + titleEn: 'Success', + description: '성공 · 완료 · 정상 (초록 계열)', + }, + muted: { + key: 'muted', + titleKo: '중립', + titleEn: 'Muted', + description: '비활성 · 중립 · 기타 (회색 계열)', + }, + purple: { + key: 'purple', + titleKo: '분석', + titleEn: 'Purple', + description: '분석 · AI · 특수 (보라 계열)', + }, + cyan: { + key: 'cyan', + titleKo: '모니터', + titleEn: 'Cyan', + description: '모니터링 · 스트림 (청록 계열)', + }, +}; + +export const BADGE_INTENT_ORDER: BadgeIntent[] = [ + 'critical', + 'high', + 'warning', + 'info', + 'success', + 'muted', + 'purple', + 'cyan', +]; + +export const BADGE_SIZE_ORDER: BadgeSize[] = ['xs', 'sm', 'md', 'lg']; + +// ── Button variant 의미 ──────────────────────── +export const BUTTON_VARIANT_META: Record> = { + primary: { + key: 'primary', + titleKo: '주요', + titleEn: 'Primary', + description: '주요 액션 · 기본 CTA (페이지당 1개 권장)', + }, + secondary: { + key: 'secondary', + titleKo: '보조', + titleEn: 'Secondary', + description: '보조 액션 · 툴바 버튼 (기본값)', + }, + ghost: { + key: 'ghost', + titleKo: '고요', + titleEn: 'Ghost', + description: '고요한 액션 · 리스트 행 내부', + }, + outline: { + key: 'outline', + titleKo: '윤곽', + titleEn: 'Outline', + description: '강조 보조 · 필터 활성화 상태', + }, + destructive: { + key: 'destructive', + titleKo: '위험', + titleEn: 'Destructive', + description: '삭제 · 비활성화 등 위험 액션', + }, +}; + +export const BUTTON_VARIANT_ORDER: ButtonVariant[] = [ + 'primary', + 'secondary', + 'ghost', + 'outline', + 'destructive', +]; + +export const BUTTON_SIZE_ORDER: ButtonSize[] = ['sm', 'md', 'lg']; diff --git a/frontend/src/shared/constants/catalogRegistry.ts b/frontend/src/shared/constants/catalogRegistry.ts new file mode 100644 index 0000000..9b4aabd --- /dev/null +++ b/frontend/src/shared/constants/catalogRegistry.ts @@ -0,0 +1,267 @@ +/** + * 분류 카탈로그 중앙 레지스트리 (Single Source of Truth) + * + * 디자인 쇼케이스의 카탈로그 섹션과 실제 프론트가 **모두 이 레지스트리를 참조**한다. + * 따라서: + * - 특정 분류의 라벨을 바꾸면 (예: '심각' → '매우 심각') 쇼케이스 + 실 페이지 동시 반영 + * - 특정 분류의 intent/색상을 바꾸면 역시 자동 반영 + * - 새 카탈로그를 추가하면 쇼케이스에 자동 노출 (등록만 하면 됨) + * + * 각 카탈로그는 `items: Record` 형태를 가진다 (Meta는 개별 파일의 타입 사용). + * 쇼케이스는 heterogeneous 타입을 허용하므로 CatalogEntry의 items는 Record 로 처리. + */ + +import { ALERT_LEVELS } from './alertLevels'; +import { VIOLATION_TYPES } from './violationTypes'; +import { EVENT_STATUSES } from './eventStatuses'; +import { ENFORCEMENT_ACTIONS } from './enforcementActions'; +import { ENFORCEMENT_RESULTS } from './enforcementResults'; +import { PATROL_STATUSES } from './patrolStatuses'; +import { ENGINE_SEVERITIES } from './engineSeverities'; +import { DEVICE_STATUSES } from './deviceStatuses'; +import { + PARENT_RESOLUTION_STATUSES, + LABEL_SESSION_STATUSES, +} from './parentResolutionStatuses'; +import { + MODEL_STATUSES, + QUALITY_GATE_STATUSES, + EXPERIMENT_STATUSES, +} from './modelDeploymentStatuses'; +import { GEAR_GROUP_TYPES } from './gearGroupTypes'; +import { DARK_VESSEL_PATTERNS } from './darkVesselPatterns'; +import { USER_ACCOUNT_STATUSES } from './userAccountStatuses'; +import { LOGIN_RESULTS } from './loginResultStatuses'; +import { PERMIT_STATUSES, GEAR_JUDGMENTS } from './permissionStatuses'; +import { + VESSEL_SURVEILLANCE_STATUSES, + VESSEL_RISK_RINGS, +} from './vesselAnalysisStatuses'; +import { CONNECTION_STATUSES } from './connectionStatuses'; +import { TRAINING_ZONE_TYPES } from './trainingZoneTypes'; + +/** + * 카탈로그 공통 메타 — 쇼케이스 렌더와 UI 일관성을 위한 최소 스키마 + */ +export interface CatalogEntry { + /** 안정적 ID (쇼케이스 추적 ID의 슬러그 부분) */ + id: string; + /** 쇼케이스 추적 ID (TRK-CAT-*) */ + showcaseId: string; + /** 쇼케이스 섹션 제목 (한글) */ + titleKo: string; + /** 쇼케이스 섹션 제목 (영문) */ + titleEn: string; + /** 1줄 설명 */ + description: string; + /** 출처 (백엔드 enum / code_master 등) */ + source?: string; + /** 카탈로그 데이터 — items의 각 meta는 { code, fallback?, intent?, classes?, ... } 구조 */ + items: Record; +} + +/** + * 전체 카탈로그 레지스트리 + * + * ⚠️ 새 카탈로그 추가 시: + * 1. shared/constants/에 파일 생성 (items record + 헬퍼 함수) + * 2. 이 레지스트리에 CatalogEntry 추가 + * 3. 쇼케이스에 자동 노출 + 실 페이지에서 헬퍼 함수로 참조 + */ +export const CATALOG_REGISTRY: CatalogEntry[] = [ + { + id: 'alert-level', + showcaseId: 'TRK-CAT-alert-level', + titleKo: '위험도', + titleEn: 'Alert Level', + description: 'CRITICAL / HIGH / MEDIUM / LOW — 모든 이벤트/알림 배지', + source: 'backend code_master EVENT_LEVEL', + items: ALERT_LEVELS, + }, + { + id: 'violation-type', + showcaseId: 'TRK-CAT-violation-type', + titleKo: '위반 유형', + titleEn: 'Violation Type', + description: '중국불법조업 / 환적의심 / EEZ침범 등', + source: 'backend ViolationType enum', + items: VIOLATION_TYPES, + }, + { + id: 'event-status', + showcaseId: 'TRK-CAT-event-status', + titleKo: '이벤트 상태', + titleEn: 'Event Status', + description: 'NEW / ACK / IN_PROGRESS / RESOLVED / FALSE_POSITIVE', + source: 'backend code_master EVENT_STATUS', + items: EVENT_STATUSES, + }, + { + id: 'enforcement-action', + showcaseId: 'TRK-CAT-enforcement-action', + titleKo: '단속 조치', + titleEn: 'Enforcement Action', + description: 'CAPTURE / INSPECT / WARN / DISPERSE / TRACK / EVIDENCE', + source: 'backend code_master ENFORCEMENT_ACTION', + items: ENFORCEMENT_ACTIONS, + }, + { + id: 'enforcement-result', + showcaseId: 'TRK-CAT-enforcement-result', + titleKo: '단속 결과', + titleEn: 'Enforcement Result', + description: 'PUNISHED / REFERRED / WARNED / RELEASED / FALSE_POSITIVE', + source: 'backend code_master ENFORCEMENT_RESULT', + items: ENFORCEMENT_RESULTS, + }, + { + id: 'patrol-status', + showcaseId: 'TRK-CAT-patrol-status', + titleKo: '함정 상태', + titleEn: 'Patrol Status', + description: '출동 / 순찰 / 복귀 / 정박 / 정비 / 대기', + source: 'backend code_master PATROL_STATUS', + items: PATROL_STATUSES, + }, + { + id: 'engine-severity', + showcaseId: 'TRK-CAT-engine-severity', + titleKo: '엔진 심각도', + titleEn: 'Engine Severity', + description: 'AI 모델/분석엔진 오류 심각도', + items: ENGINE_SEVERITIES, + }, + { + id: 'device-status', + showcaseId: 'TRK-CAT-device-status', + titleKo: '함정 Agent 장치 상태', + titleEn: 'Device Status', + description: 'ONLINE / OFFLINE / SYNCING / NOT_DEPLOYED', + items: DEVICE_STATUSES, + }, + { + id: 'parent-resolution', + showcaseId: 'TRK-CAT-parent-resolution', + titleKo: '모선 확정 상태', + titleEn: 'Parent Resolution Status', + description: 'PENDING / CONFIRMED / REJECTED / REVIEWING', + items: PARENT_RESOLUTION_STATUSES, + }, + { + id: 'label-session', + showcaseId: 'TRK-CAT-label-session', + titleKo: '라벨 세션', + titleEn: 'Label Session Status', + description: '모선 학습 세션 상태', + items: LABEL_SESSION_STATUSES, + }, + { + id: 'model-status', + showcaseId: 'TRK-CAT-model-status', + titleKo: 'AI 모델 상태', + titleEn: 'Model Status', + description: 'DEV / STAGING / CANARY / PROD / ARCHIVED', + items: MODEL_STATUSES, + }, + { + id: 'quality-gate', + showcaseId: 'TRK-CAT-quality-gate', + titleKo: '품질 게이트', + titleEn: 'Quality Gate Status', + description: '모델 배포 전 품질 검증', + items: QUALITY_GATE_STATUSES, + }, + { + id: 'experiment', + showcaseId: 'TRK-CAT-experiment', + titleKo: 'ML 실험', + titleEn: 'Experiment Status', + description: 'MLOps 실험 상태', + items: EXPERIMENT_STATUSES, + }, + { + id: 'gear-group', + showcaseId: 'TRK-CAT-gear-group', + titleKo: '어구 그룹 유형', + titleEn: 'Gear Group Type', + description: 'FLEET / GEAR_IN_ZONE / GEAR_OUT_ZONE', + items: GEAR_GROUP_TYPES, + }, + { + id: 'dark-vessel', + showcaseId: 'TRK-CAT-dark-vessel', + titleKo: '다크베셀 패턴', + titleEn: 'Dark Vessel Pattern', + description: 'AIS 끊김/스푸핑 패턴 5종', + items: DARK_VESSEL_PATTERNS, + }, + { + id: 'user-account', + showcaseId: 'TRK-CAT-user-account', + titleKo: '사용자 계정 상태', + titleEn: 'User Account Status', + description: 'ACTIVE / LOCKED / INACTIVE / PENDING', + items: USER_ACCOUNT_STATUSES, + }, + { + id: 'login-result', + showcaseId: 'TRK-CAT-login-result', + titleKo: '로그인 결과', + titleEn: 'Login Result', + description: 'SUCCESS / FAILED / LOCKED', + items: LOGIN_RESULTS, + }, + { + id: 'permit-status', + showcaseId: 'TRK-CAT-permit-status', + titleKo: '허가 상태', + titleEn: 'Permit Status', + description: '선박 허가 유효 / 만료 / 정지', + items: PERMIT_STATUSES, + }, + { + id: 'gear-judgment', + showcaseId: 'TRK-CAT-gear-judgment', + titleKo: '어구 판정', + titleEn: 'Gear Judgment', + description: '합법 / 의심 / 불법', + items: GEAR_JUDGMENTS, + }, + { + id: 'vessel-surveillance', + showcaseId: 'TRK-CAT-vessel-surveillance', + titleKo: '선박 감시 상태', + titleEn: 'Vessel Surveillance Status', + description: '관심선박 추적 상태', + items: VESSEL_SURVEILLANCE_STATUSES, + }, + { + id: 'vessel-risk-ring', + showcaseId: 'TRK-CAT-vessel-risk-ring', + titleKo: '선박 위험 링', + titleEn: 'Vessel Risk Ring', + description: '지도 마커 위험도 링', + items: VESSEL_RISK_RINGS, + }, + { + id: 'connection', + showcaseId: 'TRK-CAT-connection', + titleKo: '연결 상태', + titleEn: 'Connection Status', + description: 'OK / WARNING / ERROR', + items: CONNECTION_STATUSES, + }, + { + id: 'training-zone', + showcaseId: 'TRK-CAT-training-zone', + titleKo: '훈련 수역', + titleEn: 'Training Zone Type', + description: 'NAVY / AIRFORCE / ARMY / ADD / KCG', + items: TRAINING_ZONE_TYPES, + }, +]; + +/** ID로 특정 카탈로그 조회 */ +export function getCatalogById(id: string): CatalogEntry | undefined { + return CATALOG_REGISTRY.find((c) => c.id === id); +}