diff --git a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx index 2a356aa43..05db9169a 100644 --- a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx +++ b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx @@ -1,11 +1,13 @@ "use client"; import { useMemo, useState } from "react"; +import { useRouter } from "next/navigation"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import DataGrid from "@/components/module/DataGrid"; +import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { Button, Checkbox, + DataTable, Dialog, DialogContent, DialogHeader, @@ -27,14 +29,17 @@ import { TableRow, TableCell, } from "@common/components"; -import { verificationHistoryColumnInfo } from "@/entities/statistics/model/verification-history-column"; +import { verificationHistoryColumns } from "@/entities/statistics/model/verification-history-columns"; import { analysisApi } from "@/entities/statistics/api/analysis-api"; import { analysisHistoryApi } from "@/entities/statistics/api/analysis-history-api"; import type { AnalysisRequestPayload, AnalysisResponseDto, } from "@/entities/statistics/model/analysis-interface"; -import type { StAnlsOutlierListResponse } from "@/entities/statistics/model/analysis-history-interface"; +import type { + StAnlsMstResponse, + StAnlsOutlierListResponse, +} from "@/entities/statistics/model/analysis-history-interface"; import { ENDPOINT } from "@/shared/config"; import { useAlertStore } from "@/shared/lib"; import { CodeApi } from "@/entities/code"; @@ -70,6 +75,7 @@ function toNumber(value: string): number { } const HbStatisticsVerification = () => { + const router = useRouter(); const { setMessage: alert } = useAlertStore((state) => state); const queryClient = useQueryClient(); @@ -103,20 +109,34 @@ const HbStatisticsVerification = () => { const { data: analysisHistory } = useQuery({ queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST], - queryFn: async () => { - const response = await analysisHistoryApi.getAnalysisHistoryList({ + queryFn: () => + analysisHistoryApi.getAnalysisHistoryList({ page: 0, size: 10, - }); - return response; + }), + refetchInterval: (query) => { + const data = query.state.data; + if (!data?.content?.length) return false; + const hasProcessing = (data.content as StAnlsMstResponse[]).some( + (item) => item.anlsSttsCd === "PROCESSING", + ); + return hasProcessing ? 3000 : false; }, }); - const historyRowData = - (analysisHistory?.content ?? []) as unknown as Record< - string, - string | number - >[]; + const historyRowData: StAnlsMstResponse[] = (analysisHistory?.content ?? + []) as StAnlsMstResponse[]; + + const table = useReactTable({ + data: historyRowData, + columns: verificationHistoryColumns, + getCoreRowModel: getCoreRowModel(), + meta: { + onOutlierCellClick: (row: StAnlsMstResponse) => { + setOutlierDialog({ open: true, anlsSn: row.anlsSn }); + }, + }, + }); const { data: outlierList, isLoading: isLoadingOutlier } = useQuery({ queryKey: [ @@ -212,11 +232,10 @@ const HbStatisticsVerification = () => { setSelectedTargets([]); } - function handleHistoryRowClick(row: Record) { - const abnormalCount = Number(row.otlrDataNocs ?? 0); + function handleHistoryRowClick(row: StAnlsMstResponse) { const anlsSn = Number(row.anlsSn ?? 0); - if (!anlsSn || abnormalCount <= 0) return; - setOutlierDialog({ open: true, anlsSn }); + if (!anlsSn) return; + router.push(`/statistics/verification/${anlsSn}`); } function closeOutlierDialog() { @@ -417,25 +436,16 @@ const HbStatisticsVerification = () => { )} -
+

검증 이력

- {}} - isExcelImport={false} - isExcelExport={false} - showHeaderCheckbox={false} + { - if (fieldName === "otlrDataNocs") { - const abnormal = Number(row.otlrDataNocs ?? 0); - if (abnormal > 0) return "rg-cell-verify-error"; - } - return ""; - }} + viewPagination={true} + className="[&_td]:!p-0" />
diff --git a/apps/st/src/entities/statistics/model/analysis-history-interface.ts b/apps/st/src/entities/statistics/model/analysis-history-interface.ts index c37d4fe5c..c4aa1e0eb 100644 --- a/apps/st/src/entities/statistics/model/analysis-history-interface.ts +++ b/apps/st/src/entities/statistics/model/analysis-history-interface.ts @@ -1,49 +1,49 @@ import type { PageableList } from "@/shared/model"; export interface StAnlsMstResponse { - anlsSn: number; - anlsBgngDt: string; - anlsEndDt: string | null; - anlsSttsCd: string; - anlsSttsNm: string; - nmlDataNocs: number; - exclDataNocs: number; - otlrDataNocs: number; - jobUuid: string; - regDt: string; - rgtr: string; - rgtrNm: string; + anlsSn: number; + anlsBgngDt: string; + anlsEndDt: string | null; + anlsSttsCd: string; + anlsSttsNm: string; + anlsPrgrsRt?: number; + nmlDataNocs: number; + exclDataNocs: number; + otlrDataNocs: number; + jobUuid: string; + regDt: string; + rgtr: string; + rgtrNm: string; } export interface StAnlsMstSearchParams { - anlsSttsCd?: string; - jobUuid?: string; - page?: number; - size?: number; - sort?: string; + anlsSttsCd?: string; + jobUuid?: string; + page?: number; + size?: number; + sort?: string; } export interface StAnlsOutlierDataResponse { - rsltSn: number; - vrfcTrgtSn: number; - admdstCd: string; - sido: string; - gugun: string; - adstDong: string; - addr: string; - cryrVl: number; - otlrTrsfRt: number; + rsltSn: number; + vrfcTrgtSn: number; + admdstCd: string; + sido: string; + gugun: string; + adstDong: string; + addr: string; + cryrVl: number; + otlrTrsfRt: number; } export interface StAnlsOutlierListResponse { - anlsSn: number; - vrfcTrgtSn: number; - vrfcTrgtNm: string; - nmlDataNocs: number; - exclDataNocs: number; - otlrDataNocs: number; - outlierDataList: StAnlsOutlierDataResponse[]; + anlsSn: number; + vrfcTrgtSn: number; + vrfcTrgtNm: string; + nmlDataNocs: number; + exclDataNocs: number; + otlrDataNocs: number; + outlierDataList: StAnlsOutlierDataResponse[]; } export type StAnlsMstListApiResponse = PageableList; - diff --git a/apps/st/src/entities/statistics/model/verification-history-column.ts b/apps/st/src/entities/statistics/model/verification-history-column.ts deleted file mode 100644 index e6c635a54..000000000 --- a/apps/st/src/entities/statistics/model/verification-history-column.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { ValueType } from "realgrid"; -import type { ColumnInfo } from "@rsone/components"; - -/** 검증 이력 그리드 컬럼 설정 */ -export const verificationHistoryColumnInfo: ColumnInfo = { - columns: [ - { - name: "anlsBgngDt", - fieldName: "anlsBgngDt", - header: { text: "검증 실행 일시" }, - editable: false, - sortdisable: false, - }, - { - name: "anlsSttsNm", - fieldName: "anlsSttsNm", - header: { text: "진행 상태" }, - editable: false, - sortdisable: false, - }, - { - name: "anlsEndDt", - fieldName: "anlsEndDt", - header: { text: "종료 일시" }, - editable: false, - sortdisable: false, - }, - { - name: "rgtrNm", - fieldName: "rgtrNm", - header: { text: "검증자" }, - editable: false, - sortdisable: false, - }, - { - name: "nmlDataNocs", - fieldName: "nmlDataNocs", - header: { text: "정상건수" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0", - }, - { - name: "exclDataNocs", - fieldName: "exclDataNocs", - header: { text: "제외건수" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0", - }, - { - name: "otlrDataNocs", - fieldName: "otlrDataNocs", - header: { text: "이상건수" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0", - }, - ], - columnLayout: [ - "anlsBgngDt", - "anlsSttsNm", - "anlsEndDt", - "rgtrNm", - "nmlDataNocs", - "exclDataNocs", - "otlrDataNocs", - ], -}; diff --git a/apps/st/src/entities/statistics/model/verification-history-columns.tsx b/apps/st/src/entities/statistics/model/verification-history-columns.tsx new file mode 100644 index 000000000..766709217 --- /dev/null +++ b/apps/st/src/entities/statistics/model/verification-history-columns.tsx @@ -0,0 +1,93 @@ +import { createColumnHelper } from "@tanstack/react-table"; +import { Progress } from "@common/components"; +import type { StAnlsMstResponse } from "./analysis-history-interface"; + +const columnHelper = createColumnHelper(); + +export const verificationHistoryColumns = [ + columnHelper.accessor("anlsBgngDt", { + header: "검증 실행 일시", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor( + (row) => ({ + anlsSttsCd: row.anlsSttsCd, + anlsSttsNm: row.anlsSttsNm, + anlsPrgrsRt: row.anlsPrgrsRt, + }), + { + id: "anlsSttsNm", + header: "진행 상태", + cell: (info) => { + const { anlsSttsCd, anlsSttsNm, anlsPrgrsRt } = info.getValue(); + if (anlsSttsCd === "PROCESSING") { + return ( +
+ + + {anlsPrgrsRt ?? 0}% + +
+ ); + } + return anlsSttsNm; + }, + }, + ), + columnHelper.accessor("anlsEndDt", { + header: "종료 일시", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("rgtrNm", { + header: "검증자", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("nmlDataNocs", { + header: "정상건수", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("exclDataNocs", { + header: "제외건수", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("otlrDataNocs", { + header: "이상건수", + cell: (info) => { + const value = info.getValue() ?? "-"; + const row = info.row.original; + const onOutlierCellClick = ( + info.table.options.meta as { + onOutlierCellClick?: (row: StAnlsMstResponse) => void; + } + )?.onOutlierCellClick; + if (value > 0 && onOutlierCellClick) { + return ( + + ); + } + return value; + }, + }), +]; diff --git a/apps/st/src/entities/statistics/model/verification-history-interface.ts b/apps/st/src/entities/statistics/model/verification-history-interface.ts deleted file mode 100644 index 869399cba..000000000 --- a/apps/st/src/entities/statistics/model/verification-history-interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** 검증 이력 한 건 (그리드 행 데이터) */ -export interface VerificationHistoryItem { - /** 검증 실행 일시 */ - executedAt: string; - /** 진행 상태 */ - progressStatus: string; - /** 종료 일시 */ - endedAt: string; - /** 검증년도 */ - verificationYear: string; - /** μ */ - mu: number; - /** Percentile */ - percentile: number; - /** A */ - a: number; - /** C */ - c: number; - /** 검증 대상 */ - target: string; - /** 정상건수 */ - normalCount: number; - /** 제외건수 */ - excludedCount: number; - /** 이상건수 */ - abnormalCount: number; -} diff --git a/apps/st/src/entities/statistics/model/verification-history-mock.ts b/apps/st/src/entities/statistics/model/verification-history-mock.ts deleted file mode 100644 index 90c152be7..000000000 --- a/apps/st/src/entities/statistics/model/verification-history-mock.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { fakerKO as faker } from "@faker-js/faker"; -import type { VerificationHistoryItem } from "./verification-history-interface"; - -const VERIFICATION_TARGETS = [ - "하수도 보급률", - "하수관로", - "하수관로 유지관리", - "하수저류시설 및 빗물펌프장", - "펌프장", - "공공하수처리시설", - "분뇨처리시설", - "개인하수처리시설", - "하수도재정", - "물 재이용", - "하수도 서비스 수준", -]; - -const PROGRESS_STATUSES = ["대기", "진행중", "완료", "실패"]; - -function formatDateTime(date: Date): string { - const y = date.getFullYear(); - const m = String(date.getMonth() + 1).padStart(2, "0"); - const d = String(date.getDate()).padStart(2, "0"); - const h = String(date.getHours()).padStart(2, "0"); - const min = String(date.getMinutes()).padStart(2, "0"); - return `${y}-${m}-${d} ${h}:${min}`; -} - -/** 검증 이력 목데이터 생성 (그리드 행 형식) */ -export function createVerificationHistoryMock( - count = 10, -): Record[] { - faker.seed(42); - return Array.from({ length: count }, () => { - const executedAt = faker.date.recent({ days: 90 }); - const durationMinutes = faker.number.int({ min: 1, max: 120 }); - const endedAt = new Date( - executedAt.getTime() + durationMinutes * 60 * 1000, - ); - const status = faker.helpers.arrayElement(PROGRESS_STATUSES); - const normalCount = faker.number.int({ min: 0, max: 500 }); - const excludedCount = faker.number.int({ min: 0, max: 50 }); - const abnormalCount = faker.number.int({ min: 0, max: 30 }); - const item: VerificationHistoryItem = { - executedAt: formatDateTime(executedAt), - progressStatus: status, - endedAt: formatDateTime(endedAt), - verificationYear: String(executedAt.getFullYear()), - mu: faker.number.float({ min: 0, max: 1, fractionDigits: 3 }), - percentile: faker.number.float({ - min: 0, - max: 100, - fractionDigits: 2, - }), - a: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }), - c: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }), - target: faker.helpers.arrayElement(VERIFICATION_TARGETS), - normalCount, - excludedCount, - abnormalCount, - }; - return { ...item } as Record; - }); -} - -/** 검증 이력 기본 목데이터 (10건) */ -export const verificationHistoryMockData = createVerificationHistoryMock(10);