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:
부모
64e24cea71
커밋
85cb6b40a2
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user