refactor(frontend): 복잡 페이지 PageContainer 적용 (Phase B-5)

- MonitoringDashboard: 표준 PageHeader
- MapControl: demo 배지
- RiskMap: 수집 중 배지 + secondary Button 2개 액션
- Dashboard: PageContainer 래핑 (커스텀 DEFCON 헤더는 유지)
- LiveMapView: PageContainer fullBleed + flex 레이아웃 유지
- VesselDetail: PageContainer fullBleed + -m-4 해킹 제거
- TransferDetection: PageHeader 적용

Phase B 전체 완료. 실제 프론트엔드의 모든 주요 페이지가 쇼케이스 기준
공통 컴포넌트(PageContainer/PageHeader/Button/Select/Badge)를 사용한다.
카탈로그/variant 변경 시 쇼케이스와 실 페이지 동시 반영됨.

최종 통계:
- 7개 batch에서 총 30+ 파일 마이그레이션
- PageContainer 도입률: ~100% (SPA 메인 라우트 기준)
- PageHeader 도입률: ~95%
- 신규 Button 컴포넌트 도입: admin/enforcement/parent-inference 등 주요 액션

빌드 검증:
- tsc , eslint  (경고만), vite build 
This commit is contained in:
htlee 2026-04-08 12:09:17 +09:00
부모 64e24cea71
커밋 85cb6b40a2
7개의 변경된 파일59개의 추가작업 그리고 46개의 파일을 삭제

파일 보기

@ -11,6 +11,7 @@ import {
import type { LucideIcon } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@shared/components/ui/card';
import { Badge } from '@shared/components/ui/badge';
import { PageContainer } from '@shared/components/layout';
import { AreaChart, PieChart } from '@lib/charts';
import { useKpiStore } from '@stores/kpiStore';
import { useEventStore } from '@stores/eventStore';
@ -393,8 +394,8 @@ export function Dashboard() {
const defconLabels = ['', 'DEFCON 1', 'DEFCON 2', 'DEFCON 3', 'DEFCON 4', 'DEFCON 5'];
return (
<div className="space-y-4">
{/* ── 상단 헤더 바 ── */}
<PageContainer>
{/* ── 상단 헤더 바 (DEFCON + 라이브 상태) — 커스텀 구조 유지 ── */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div>
@ -670,6 +671,6 @@ export function Dashboard() {
</div>
</CardContent>
</Card>
</div>
</PageContainer>
);
}

파일 보기

@ -2,6 +2,7 @@ import { useEffect, useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardContent } from '@shared/components/ui/card';
import { Badge } from '@shared/components/ui/badge';
import { PageContainer, PageHeader } from '@shared/components/layout';
import { Activity, AlertTriangle, Ship, Eye, Anchor, Radar, Shield, Bell, Clock, Target, ChevronRight } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { AreaChart, PieChart } from '@lib/charts';
@ -80,11 +81,13 @@ export function MonitoringDashboard() {
}));
return (
<div className="p-5 space-y-4">
<div>
<h2 className="text-lg font-bold text-heading whitespace-nowrap flex items-center gap-2"><Activity className="w-5 h-5 text-green-400" />{t('monitoring.title')}</h2>
<p className="text-[10px] text-hint mt-0.5">{t('monitoring.desc')}</p>
</div>
<PageContainer>
<PageHeader
icon={Activity}
iconColor="text-green-400"
title={t('monitoring.title')}
description={t('monitoring.desc')}
/>
{/* iran 백엔드 + Prediction 시스템 상태 (실시간) */}
<SystemStatusPanel />
@ -125,6 +128,6 @@ export function MonitoringDashboard() {
))}
</div>
</CardContent></Card>
</div>
</PageContainer>
);
}

파일 보기

@ -2,6 +2,8 @@ import { useState, useRef, useCallback } from 'react';
import { BaseMap, STATIC_LAYERS, createHeatmapLayer, useMapLayers, type MapHandle } from '@lib/map';
import type { HeatPoint } from '@lib/map';
import { Card, CardContent } from '@shared/components/ui/card';
import { Button } from '@shared/components/ui/button';
import { PageContainer, PageHeader } from '@shared/components/layout';
import { Map, Layers, Clock, BarChart3, AlertTriangle, Printer, Download, Ship, TrendingUp } from 'lucide-react';
import { AreaChart as EcAreaChart, LineChart as EcLineChart, PieChart as EcPieChart, BarChart as EcBarChart } from '@lib/charts';
@ -175,20 +177,24 @@ export function RiskMap() {
useMapLayers(mapRef, buildLayers, [tab]);
return (
<div className="p-5 space-y-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-bold text-heading flex items-center gap-2">
<Map className="w-5 h-5 text-red-400" />
<PageContainer>
<PageHeader
icon={Map}
iconColor="text-red-400"
title="격자 기반 불법조업 위험도 지도"
description="SFR-05 | 위험도 히트맵 (수집 중) + MTIS 해양사고 통계 (중앙해양안전심판원)"
actions={
<>
{COLLECTING_BADGE}
</h2>
<p className="text-[10px] text-hint mt-0.5">SFR-05 | ( ) + MTIS ()</p>
</div>
<div className="flex gap-1.5">
<button className="flex items-center gap-1 px-2.5 py-1.5 bg-surface-overlay border border-border rounded-lg text-[10px] text-muted-foreground hover:text-heading"><Printer className="w-3 h-3" /></button>
<button className="flex items-center gap-1 px-2.5 py-1.5 bg-surface-overlay border border-border rounded-lg text-[10px] text-muted-foreground hover:text-heading"><Download className="w-3 h-3" /></button>
</div>
</div>
<Button variant="secondary" size="sm" icon={<Printer className="w-3 h-3" />}>
</Button>
<Button variant="secondary" size="sm" icon={<Download className="w-3 h-3" />}>
</Button>
</>
}
/>
{/* 탭 */}
<div className="flex gap-0 border-b border-border">
@ -489,6 +495,6 @@ export function RiskMap() {
</CardContent></Card>
</div>
)}
</div>
</PageContainer>
);
}

파일 보기

@ -4,6 +4,7 @@ import { BaseMap, STATIC_LAYERS, createMarkerLayer, createRadiusLayer, useMapLay
import type { MarkerData } from '@lib/map';
import { Card, CardContent } from '@shared/components/ui/card';
import { Badge } from '@shared/components/ui/badge';
import { PageContainer } from '@shared/components/layout';
import { AlertTriangle, Ship, Radio, Zap, Activity, Clock, Pin, Loader2, WifiOff } from 'lucide-react';
import {
fetchVesselAnalysis,
@ -227,7 +228,7 @@ export function LiveMapView() {
}, [selectedEvent]);
return (
<div className="flex gap-5 h-[calc(100vh-7rem)]">
<PageContainer fullBleed className="flex gap-5 h-[calc(100vh-7rem)]">
{/* 좌측: 이벤트 목록 + 지도 */}
<div className="flex-1 flex gap-4 min-w-0">
{/* 이벤트 카드 목록 */}
@ -391,6 +392,6 @@ export function LiveMapView() {
</Card>
</div>
)}
</div>
</PageContainer>
);
}

파일 보기

@ -2,6 +2,7 @@ import { useState, useRef, useCallback } from 'react';
import { BaseMap, STATIC_LAYERS, createMarkerLayer, createRadiusLayer, useMapLayers, type MapHandle } from '@lib/map';
import { Card, CardContent } from '@shared/components/ui/card';
import { Badge } from '@shared/components/ui/badge';
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';
@ -244,18 +245,14 @@ export function MapControl() {
useMapLayers(mapRef, buildLayers, [visibleZones]);
return (
<div className="p-5 space-y-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-bold text-heading flex items-center gap-2">
<Map className="w-5 h-5 text-cyan-400" />
<span className="inline-flex items-center gap-1 px-2 py-1 bg-yellow-500/15 border border-yellow-500/30 rounded text-[10px] text-yellow-400 font-normal">
<span></span><span> ( API )</span>
</span>
</h2>
<p className="text-[10px] text-hint mt-0.5"> No.462 | Chart of Firing and Bombing Exercise Areas | WGS-84 | 출처: 국립해양조사원</p>
</div>
</div>
<PageContainer>
<PageHeader
icon={Map}
iconColor="text-cyan-400"
title="해역 통제"
description="한국연안 해상사격 훈련구역도 No.462 | Chart of Firing and Bombing Exercise Areas | WGS-84 | 출처: 국립해양조사원"
demo
/>
{/* KPI */}
<div className="flex gap-2">
@ -428,6 +425,6 @@ export function MapControl() {
/>
</>
)}
</div>
</PageContainer>
);
}

파일 보기

@ -1,13 +1,17 @@
import { Card, CardContent } from '@shared/components/ui/card';
import { PageContainer, PageHeader } from '@shared/components/layout';
import { RefreshCw } from 'lucide-react';
import { RealTransshipSuspects } from '@features/detection/RealVesselAnalysis';
export function TransferDetection() {
return (
<div className="p-5 space-y-4">
<div>
<h2 className="text-xl font-bold text-heading">· </h2>
<p className="text-xs text-hint mt-0.5"> </p>
</div>
<PageContainer>
<PageHeader
icon={RefreshCw}
iconColor="text-cyan-400"
title="환적·접촉 탐지"
description="선박 간 근접 접촉 및 환적 의심 행위 분석"
/>
{/* prediction 분석 결과 기반 실시간 환적 의심 선박 */}
<RealTransshipSuspects />
@ -32,6 +36,6 @@ export function TransferDetection() {
</div>
</CardContent>
</Card>
</div>
</PageContainer>
);
}

파일 보기

@ -1,6 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import { Badge } from '@shared/components/ui/badge';
import { PageContainer } from '@shared/components/layout';
import {
Search,
Ship, AlertTriangle, Radar, MapPin, Printer,
@ -157,7 +158,7 @@ export function VesselDetail() {
const riskMeta = ALERT_LEVELS[riskLevel] ?? ALERT_LEVELS.LOW;
return (
<div className="flex h-[calc(100vh-7.5rem)] gap-0 -m-4">
<PageContainer fullBleed className="flex h-[calc(100vh-7.5rem)] gap-0">
{/* ── 좌측: 선박 정보 패널 ── */}
<div className="w-[370px] shrink-0 bg-card border-r border-border flex flex-col overflow-hidden">
@ -427,6 +428,6 @@ export function VesselDetail() {
<button className="flex flex-col items-center py-1 text-hint hover:text-label"><div className="w-3.5 h-3.5 border border-slate-500 rounded-sm" /><span className="text-[6px]"></span></button>
<button className="flex flex-col items-center py-1 bg-blue-600/20 text-blue-400 rounded"><Radar className="w-3.5 h-3.5" /><span className="text-[6px]">AI모드</span></button>
</div>
</div>
</PageContainer>
);
}