- badgeVariants 다크 팔레트를 classes 기반 4종 카탈로그와 동일 패턴으로 통일 - 이전: dark:bg-X-400 dark:text-slate-900 dark:border-X-600 (솔리드) - 이후: dark:bg-X-500/20 dark:text-X-400 dark:border-X-500/30 (translucent) - 라이트 팔레트는 그대로 유지 (이미 통일되어 있음) - CatalogSection: 각 카탈로그 항목을 [code / 한글 배지 / 영문 배지] 3열 행으로 렌더 - 한글/영문 라벨 두 버전을 한눈에 비교 검토 가능 - 추적 ID Trk는 행 전체를 감싸서 호버/복사 동작
304 lines
10 KiB
TypeScript
304 lines
10 KiB
TypeScript
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 { 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';
|
|
|
|
/** 카탈로그 메타 공통 속성 (일부만 있을 수 있음) */
|
|
interface AnyMeta {
|
|
code: string;
|
|
intent?: BadgeIntent;
|
|
fallback?: { ko: string; en: string };
|
|
classes?: string | { bg?: string; text?: string; border?: string };
|
|
label?: string;
|
|
}
|
|
|
|
type AnyCatalog = Record<string, AnyMeta>;
|
|
|
|
function getKoLabel(meta: AnyMeta): string {
|
|
return meta.fallback?.ko ?? meta.label ?? meta.code;
|
|
}
|
|
|
|
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) {
|
|
return [meta.classes.bg, meta.classes.text, meta.classes.border].filter(Boolean).join(' ');
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/** 단일 배지 렌더 — Badge(intent) 또는 span(classes) */
|
|
function renderBadge(meta: AnyMeta, label: string): ReactNode {
|
|
if (meta.intent) {
|
|
return (
|
|
<Badge intent={meta.intent} size="sm">
|
|
{label}
|
|
</Badge>
|
|
);
|
|
}
|
|
const classes =
|
|
getFallbackClasses(meta) ??
|
|
'bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-500/20 dark:text-slate-300 dark:border-slate-500/30';
|
|
return (
|
|
<span
|
|
className={`inline-flex items-center px-2 py-0.5 rounded-md border text-[12px] font-semibold ${classes}`}
|
|
>
|
|
{label}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
/** 카탈로그 항목을 행 단위로 렌더: [code] [한글 배지] [영문 배지] */
|
|
function CatalogBadges({ catalog, idPrefix }: { catalog: AnyCatalog; idPrefix: string }) {
|
|
const entries = Object.values(catalog);
|
|
return (
|
|
<div className="space-y-1.5">
|
|
{entries.map((meta) => {
|
|
const koLabel = getKoLabel(meta);
|
|
const enLabel = getEnLabel(meta);
|
|
const trkId = `${idPrefix}-${meta.code}`;
|
|
return (
|
|
<Trk key={meta.code} id={trkId} className="flex items-center gap-3 rounded-sm">
|
|
<code className="text-[10px] text-hint font-mono whitespace-nowrap w-32 shrink-0 truncate">
|
|
{meta.code}
|
|
</code>
|
|
<div className="flex-1">{renderBadge(meta, koLabel)}</div>
|
|
<div className="flex-1">
|
|
{enLabel ? (
|
|
renderBadge(meta, enLabel)
|
|
) : (
|
|
<span className="text-[10px] text-hint italic">(no en)</span>
|
|
)}
|
|
</div>
|
|
</Trk>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<>
|
|
<TrkSectionHeader
|
|
id="TRK-SEC-catalog"
|
|
title="분류 카탈로그 (19+)"
|
|
description="백엔드 enum/code_master 기반 SSOT. 모든 위험도·상태·유형 배지의 단일 정의."
|
|
/>
|
|
|
|
<Trk id="TRK-CAT-intro" className="ds-sample mb-4">
|
|
<p className="text-xs text-label leading-relaxed">
|
|
페이지에서 배지를 렌더할 때는 반드시 카탈로그 API를 사용한다:
|
|
</p>
|
|
<code className="ds-code mt-2">
|
|
{`import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels';
|
|
|
|
<Badge intent={getAlertLevelIntent(event.level)} size="sm">
|
|
{getAlertLevelLabel(event.level, t, lang)}
|
|
</Badge>`}
|
|
</code>
|
|
</Trk>
|
|
|
|
{CATALOGS.map((entry) => (
|
|
<Trk key={entry.id} id={entry.id} className="ds-sample mb-3">
|
|
<div className="mb-2">
|
|
<div className="flex items-baseline gap-2">
|
|
<h3 className="text-sm font-semibold text-heading">{entry.title}</h3>
|
|
<code className="text-[10px] text-hint font-mono">{entry.id}</code>
|
|
</div>
|
|
<p className="text-[11px] text-hint">{entry.description}</p>
|
|
{entry.source && (
|
|
<p className="text-[10px] text-hint italic mt-0.5">출처: {entry.source}</p>
|
|
)}
|
|
</div>
|
|
<CatalogBadges catalog={entry.catalog} idPrefix={entry.id} />
|
|
</Trk>
|
|
))}
|
|
</>
|
|
);
|
|
}
|