## 시간 표시 KST 통일 - shared/utils/dateFormat.ts 공통 유틸 신규 (formatDateTime/formatDate/formatTime/toDateParam) - 14개 파일에서 인라인 toLocaleString → 공통 유틸 교체 ## i18n 'group.parentInference' 사이드바 미번역 수정 - ko/en common.json의 'group' 키 중복 정의를 병합 (95행 두번째 group 객체가 35행을 덮어써서 parentInference 누락) ## Dashboard/MonitoringDashboard/Statistics 더미→실 API - 백엔드 GET /api/stats/hourly 신규 (PredictionStatsHourly 엔티티/리포지토리) - Dashboard: HOURLY_DETECTION/VESSEL_TYPE/AREA_RISK 하드코딩 제거 → getHourlyStats(24) + getDailyStats(today) 결과로 useMemo 변환 - MonitoringDashboard: TREND Math.random() 제거 → getHourlyStats 기반 위험도 가중평균 + 경보 카운트 - Statistics: KPI_DATA 하드코딩 제거 → getKpiMetrics() 결과를 표 행으로 ## Store mock 의존성 제거 - eventStore.alerts/MOCK_ALERTS 제거 (MobileService는 events에서 직접 추출) - enforcementStore.plans 제거 (EnforcementPlan은 이미 직접 API 호출) - transferStore + MOCK_TRANSFERS 완전 제거 (ChinaFishing/TransferDetection은 RealTransshipSuspects 컴포넌트 사용) - mock/events.ts, mock/enforcement.ts, mock/transfers.ts 파일 삭제 ## RiskMap 랜덤 격자 제거 - generateGrid() Math.random() 제거 → 빈 배열 + 'AI 분석 데이터 수집 중' 안내 - MTIS 외부 통계 5개 탭에 [MTIS 외부 통계] 배지 추가 ## 12개 mock 화면에 '데모 데이터' 노란색 배지 추가 - patrol/PatrolRoute, FleetOptimization - admin/AdminPanel, DataHub, NoticeManagement, SystemConfig - ai-operations/AIModelManagement, MLOpsPage - field-ops/ShipAgent - statistics/ReportManagement, ExternalService - surveillance/MapControl ## 백엔드 NUMERIC precision 동기화 - PredictionKpi.deltaPct: 5,2 → 12,2 - PredictionStatsDaily/Monthly.aiAccuracyPct: 5,2 → 12,2 - (V015 마이그레이션과 동기화) 44 files changed, +346 / -787 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
57 lines
1.4 KiB
TypeScript
57 lines
1.4 KiB
TypeScript
import { create } from 'zustand';
|
|
import type { KpiMetric, MonthlyTrend, ViolationType } from '@/data/mock/kpi';
|
|
import {
|
|
getKpiMetrics,
|
|
getMonthlyStats,
|
|
toKpiMetric,
|
|
toMonthlyTrend,
|
|
toViolationTypes,
|
|
} from '@/services/kpi';
|
|
import { toDateParam } from '@shared/utils/dateFormat';
|
|
|
|
interface KpiStore {
|
|
metrics: KpiMetric[];
|
|
monthly: MonthlyTrend[];
|
|
violationTypes: ViolationType[];
|
|
loaded: boolean;
|
|
loading: boolean;
|
|
error: string | null;
|
|
load: () => Promise<void>;
|
|
}
|
|
|
|
export const useKpiStore = create<KpiStore>((set, get) => ({
|
|
metrics: [],
|
|
monthly: [],
|
|
violationTypes: [],
|
|
loaded: false,
|
|
loading: false,
|
|
error: null,
|
|
load: async () => {
|
|
if (get().loading) return;
|
|
set({ loading: true, error: null });
|
|
try {
|
|
// 6개월 범위로 월별 통계 조회
|
|
const now = new Date();
|
|
const from = new Date(now.getFullYear(), now.getMonth() - 6, 1);
|
|
|
|
const [kpiData, monthlyData] = await Promise.all([
|
|
getKpiMetrics(),
|
|
getMonthlyStats(toDateParam(from), toDateParam(now)),
|
|
]);
|
|
|
|
set({
|
|
metrics: kpiData.map(toKpiMetric),
|
|
monthly: monthlyData.map(toMonthlyTrend),
|
|
violationTypes: toViolationTypes(monthlyData),
|
|
loaded: true,
|
|
loading: false,
|
|
});
|
|
} catch (err) {
|
|
set({
|
|
error: err instanceof Error ? err.message : 'KPI 데이터 로드 실패',
|
|
loading: false,
|
|
});
|
|
}
|
|
},
|
|
}));
|