[st] feat: 검증 이력 테이블 수정
This commit is contained in:
@@ -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<string, string | number>) {
|
||||
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 = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-10 px-2">
|
||||
<div className="mt-10 px-4 pb-8">
|
||||
<h3 className="mb-2 px-2 text-[1.5rem] font-semibold text-title">
|
||||
검증 이력
|
||||
</h3>
|
||||
<DataGrid
|
||||
columnInfo={verificationHistoryColumnInfo}
|
||||
rowData={historyRowData}
|
||||
setRowData={() => {}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={false}
|
||||
showHeaderCheckbox={false}
|
||||
<DataTable
|
||||
table={table}
|
||||
variant="secondary"
|
||||
onRowClick={handleHistoryRowClick}
|
||||
cellStyleCallback={({ row, fieldName }) => {
|
||||
if (fieldName === "otlrDataNocs") {
|
||||
const abnormal = Number(row.otlrDataNocs ?? 0);
|
||||
if (abnormal > 0) return "rg-cell-verify-error";
|
||||
}
|
||||
return "";
|
||||
}}
|
||||
viewPagination={true}
|
||||
className="[&_td]:!p-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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<StAnlsMstResponse>;
|
||||
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
};
|
||||
@@ -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<StAnlsMstResponse>();
|
||||
|
||||
export const verificationHistoryColumns = [
|
||||
columnHelper.accessor("anlsBgngDt", {
|
||||
header: "검증 실행 일시",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
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 (
|
||||
<div className="flex min-w-[8rem] items-center gap-2">
|
||||
<Progress
|
||||
value={anlsPrgrsRt ?? 0}
|
||||
className="h-2 flex-1"
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{anlsPrgrsRt ?? 0}%
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return anlsSttsNm;
|
||||
},
|
||||
},
|
||||
),
|
||||
columnHelper.accessor("anlsEndDt", {
|
||||
header: "종료 일시",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("rgtrNm", {
|
||||
header: "검증자",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("nmlDataNocs", {
|
||||
header: "정상건수",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("exclDataNocs", {
|
||||
header: "제외건수",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
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 (
|
||||
<button
|
||||
type="button"
|
||||
className="block w-full cursor-pointer bg-red-50 py-4 text-destructive underline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onOutlierCellClick(row);
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<string, string | number>[] {
|
||||
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<string, string | number>;
|
||||
});
|
||||
}
|
||||
|
||||
/** 검증 이력 기본 목데이터 (10건) */
|
||||
export const verificationHistoryMockData = createVerificationHistoryMock(10);
|
||||
Reference in New Issue
Block a user