From 2976796652bb842e51d876fbdfba8139b75222a2 Mon Sep 17 00:00:00 2001 From: htlee Date: Wed, 8 Apr 2026 11:57:01 +0900 Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20enforcement/field-ops/pat?= =?UTF-8?q?rol=20PageContainer/PageHeader=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EnforcementHistory/EventList/EnforcementPlan: primary Button 액션 - EventList: Select 공통 컴포넌트로 등급 필터 치환 - AIAlert/ShipAgent/MobileService: PageContainer + PageHeader(demo) - PatrolRoute/FleetOptimization: primary Button 액션 2개씩 Phase B-3 완료. 총 10개 파일. --- .../enforcement/EnforcementHistory.tsx | 18 ++--- .../src/features/enforcement/EventList.tsx | 77 +++++++++---------- frontend/src/features/field-ops/AIAlert.tsx | 44 ++++++----- .../src/features/field-ops/MobileService.tsx | 15 ++-- frontend/src/features/field-ops/ShipAgent.tsx | 24 +++--- .../src/features/patrol/FleetOptimization.tsx | 47 ++++++----- frontend/src/features/patrol/PatrolRoute.tsx | 39 +++++----- .../risk-assessment/EnforcementPlan.tsx | 24 +++--- 8 files changed, 155 insertions(+), 133 deletions(-) diff --git a/frontend/src/features/enforcement/EnforcementHistory.tsx b/frontend/src/features/enforcement/EnforcementHistory.tsx index c04f8fd..418fe02 100644 --- a/frontend/src/features/enforcement/EnforcementHistory.tsx +++ b/frontend/src/features/enforcement/EnforcementHistory.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo } from 'react'; 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 { FileText, CheckCircle, XCircle, Loader2 } from 'lucide-react'; import { useEnforcementStore } from '@stores/enforcementStore'; @@ -121,14 +122,13 @@ export function EnforcementHistory() { const DATA: Record[] = records as Record[]; return ( -
-
-

- - {t('history.title')} -

-

{t('history.desc')}

-
+ + {/* KPI 카드 — backend enum 코드(PUNISHED/REFERRED/FALSE_POSITIVE) 기반 비교 */}
@@ -188,6 +188,6 @@ export function EnforcementHistory() { exportFilename="단속이력" /> )} -
+
); } diff --git a/frontend/src/features/enforcement/EventList.tsx b/frontend/src/features/enforcement/EventList.tsx index 26e47d5..e3c2b88 100644 --- a/frontend/src/features/enforcement/EventList.tsx +++ b/frontend/src/features/enforcement/EventList.tsx @@ -1,6 +1,9 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Badge } from '@shared/components/ui/badge'; +import { Button } from '@shared/components/ui/button'; +import { Select } from '@shared/components/ui/select'; +import { PageContainer, PageHeader } from '@shared/components/layout'; import { DataTable, type DataColumn } from '@shared/components/common/DataTable'; import { FileUpload } from '@shared/components/common/FileUpload'; import { @@ -135,45 +138,41 @@ export function EventList() { const kpiTotal = (stats['TOTAL'] as number | undefined) ?? EVENTS.length; return ( -
- {/* 헤더 */} -
-
-

- - {t('eventList.title')} -

-

- {t('eventList.desc')} -

-
-
- {/* 등급 필터 */} -
- - setLevelFilter(e.target.value)} + title="등급 필터" + className="w-32" + > + + + + + + +
+
- -
-
+ 파일 업로드 + + + } + /> {/* KPI 요약 */}
@@ -248,6 +247,6 @@ export function EventList() { exportFilename="이벤트목록" /> )} -
+ ); } diff --git a/frontend/src/features/field-ops/AIAlert.tsx b/frontend/src/features/field-ops/AIAlert.tsx index 66b5d42..ef78be7 100644 --- a/frontend/src/features/field-ops/AIAlert.tsx +++ b/frontend/src/features/field-ops/AIAlert.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; 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 { Send, Loader2, AlertTriangle } from 'lucide-react'; import { getAlerts, type PredictionAlert } from '@/services/event'; @@ -140,34 +141,37 @@ export function AIAlert() { if (loading) { return ( -
- - 알림 데이터 로딩 중... -
+ +
+ + 알림 데이터 로딩 중... +
+
); } if (error) { return ( -
- - 알림 조회 실패: {error} - -
+ +
+ + 알림 조회 실패: {error} + +
+
); } return ( -
-
-

- - {t('aiAlert.title')} -

-

{t('aiAlert.desc')}

-
+ +
{[ { l: '총 발송', v: totalElements, c: 'text-heading' }, @@ -191,6 +195,6 @@ export function AIAlert() { searchKeys={['channel', 'recipient']} exportFilename="AI알림이력" /> -
+
); } diff --git a/frontend/src/features/field-ops/MobileService.tsx b/frontend/src/features/field-ops/MobileService.tsx index 637edc2..135de7f 100644 --- a/frontend/src/features/field-ops/MobileService.tsx +++ b/frontend/src/features/field-ops/MobileService.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useCallback } 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 { Smartphone, MapPin, Bell, Wifi, WifiOff, Shield, AlertTriangle, Navigation } from 'lucide-react'; import { BaseMap, createMarkerLayer, createPolylineLayer, useMapLayers, type MapHandle, type MarkerData } from '@lib/map'; import { useEventStore } from '@stores/eventStore'; @@ -52,11 +53,13 @@ export function MobileService() { ); return ( -
-
-

{t('mobileService.title')}

-

{t('mobileService.desc')}

-
+ +
{/* 모바일 프리뷰 */} @@ -143,6 +146,6 @@ export function MobileService() {
-
+ ); } diff --git a/frontend/src/features/field-ops/ShipAgent.tsx b/frontend/src/features/field-ops/ShipAgent.tsx index 2ea1a49..7f0f118 100644 --- a/frontend/src/features/field-ops/ShipAgent.tsx +++ b/frontend/src/features/field-ops/ShipAgent.tsx @@ -1,9 +1,9 @@ import { 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 { DataTable, type DataColumn } from '@shared/components/common/DataTable'; -import { Monitor, Ship, Wifi, WifiOff, RefreshCw, MapPin, Clock, CheckCircle } from 'lucide-react'; +import { Monitor } from 'lucide-react'; import { getDeviceStatusIntent, getDeviceStatusLabel } from '@shared/constants/deviceStatuses'; import { useSettingsStore } from '@stores/settingsStore'; @@ -44,16 +44,14 @@ export function ShipAgent() { ], [tc, lang]); return ( -
-
-

- {t('shipAgent.title')} - - 데모 데이터 (백엔드 API 미구현) - -

-

{t('shipAgent.desc')}

-
+ +
{[{ l: '전체 Agent', v: DATA.length, c: 'text-heading' }, { l: '온라인', v: DATA.filter(d => d.status === '온라인').length, c: 'text-green-400' }, { l: '오프라인', v: DATA.filter(d => d.status === '오프라인').length, c: 'text-red-400' }, { l: '미배포', v: DATA.filter(d => d.status === '미배포').length, c: 'text-muted-foreground' }].map(k => (
@@ -62,6 +60,6 @@ export function ShipAgent() { ))}
-
+
); } diff --git a/frontend/src/features/patrol/FleetOptimization.tsx b/frontend/src/features/patrol/FleetOptimization.tsx index 62810a1..b638423 100644 --- a/frontend/src/features/patrol/FleetOptimization.tsx +++ b/frontend/src/features/patrol/FleetOptimization.tsx @@ -3,6 +3,8 @@ import { useTranslation } from 'react-i18next'; import { BaseMap, STATIC_LAYERS, createMarkerLayer, createPolylineLayer, createZoneLayer, useMapLayers, type MapHandle } from '@lib/map'; import { Card, CardContent } from '@shared/components/ui/card'; 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 { usePatrolStore } from '@stores/patrolStore'; @@ -96,24 +98,31 @@ export function FleetOptimization() { useMapLayers(mapRef, buildLayers, [ships, COVERAGE, FLEET_ROUTES]); return ( -
-
-
-

- {t('fleetOptimization.title')} - - - 데모 데이터 (백엔드 API 미구현) - -

-

{t('fleetOptimization.desc')}

-
-
- - -
-
+ + + + + + } + /> {/* KPI */}
@@ -218,6 +227,6 @@ export function FleetOptimization() {
-
+ ); } diff --git a/frontend/src/features/patrol/PatrolRoute.tsx b/frontend/src/features/patrol/PatrolRoute.tsx index f5f27cc..b77a5bd 100644 --- a/frontend/src/features/patrol/PatrolRoute.tsx +++ b/frontend/src/features/patrol/PatrolRoute.tsx @@ -4,6 +4,8 @@ import type maplibregl from 'maplibre-gl'; import { BaseMap, STATIC_LAYERS, createMarkerLayer, createPolylineLayer, useMapLayers, type MapHandle } from '@lib/map'; import { Card, CardContent } from '@shared/components/ui/card'; import { Badge } from '@shared/components/ui/badge'; +import { Button } from '@shared/components/ui/button'; +import { PageContainer, PageHeader } from '@shared/components/layout'; import { Navigation, Ship, MapPin, Clock, Wind, Anchor, Play, BarChart3, Target, Settings, CheckCircle, Share2 } from 'lucide-react'; import { usePatrolStore } from '@stores/patrolStore'; @@ -93,23 +95,24 @@ export function PatrolRoute() { if (!currentShip || !route) return null; return ( -
-
-
-

- {t('patrolRoute.title')} - - - 데모 데이터 (백엔드 API 미구현) - -

-

{t('patrolRoute.desc')}

-
-
- - -
-
+ + + + + + } + />
{/* 함정 선택 */} @@ -215,6 +218,6 @@ export function PatrolRoute() {
-
+ ); } diff --git a/frontend/src/features/risk-assessment/EnforcementPlan.tsx b/frontend/src/features/risk-assessment/EnforcementPlan.tsx index 5c37d28..8b39e5f 100644 --- a/frontend/src/features/risk-assessment/EnforcementPlan.tsx +++ b/frontend/src/features/risk-assessment/EnforcementPlan.tsx @@ -2,6 +2,8 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Card, CardContent } from '@shared/components/ui/card'; 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 { Shield, AlertTriangle, Ship, Plus, Calendar, Users } from 'lucide-react'; import { BaseMap, STATIC_LAYERS, createMarkerLayer, createRadiusLayer, useMapLayers, type MapHandle } from '@lib/map'; @@ -118,14 +120,18 @@ export function EnforcementPlan() { const totalCrew = PLANS.reduce((sum, p) => sum + p.crew, 0); return ( -
-
-
-

{t('enforcementPlan.title')}

-

{t('enforcementPlan.desc')}

-
- -
+ + }> + 단속 계획 수립 + + } + /> {/* 로딩/에러 상태 */} {loading && ( @@ -185,6 +191,6 @@ export function EnforcementPlan() {
-
+ ); }