[st] rename: entities 폴더 정리
This commit is contained in:
+4
-1
@@ -39,4 +39,7 @@ next-env.d.ts
|
||||
|
||||
.env*
|
||||
!.env.example
|
||||
*.md
|
||||
*.md
|
||||
|
||||
# next-agents-md
|
||||
.next-docs/
|
||||
|
||||
@@ -15,19 +15,17 @@ import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import type { ApiErrorResponse } from "@/shared/model";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import { analysisApi } from "@/entities/statistics/api/analysis-api";
|
||||
import { analysisHistoryApi } from "@/entities/statistics/api/analysis-history-api";
|
||||
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
|
||||
import type {
|
||||
StAnlsMstResponse,
|
||||
StAnlsMstSearchParams,
|
||||
StAnlsOutlierListResponse,
|
||||
} from "@/entities/statistics/model/analysis-history-interface";
|
||||
import type {
|
||||
AnalysisRequestPayload,
|
||||
AnalysisResponseDto,
|
||||
} from "@/entities/statistics/model/analysis-interface";
|
||||
import { verificationHistoryColumns } from "@/entities/statistics/model/verification-history-columns";
|
||||
StAnlsMstResponse,
|
||||
StAnlsMstSearchParams,
|
||||
StAnlsOutlierListResponse,
|
||||
} from "@/entities/statistics";
|
||||
import { verificationHistoryColumns } from "@/entities/statistics";
|
||||
import { analysisApi } from "@/entities/statistics/api/analysis-api";
|
||||
import { analysisHistoryApi } from "@/entities/statistics/api/analysis-history-api";
|
||||
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
|
||||
import type { VerificationTargetCodeResponse } from "@/entities/statistics/model/verification-target-management-interface";
|
||||
import {
|
||||
Button,
|
||||
@@ -228,11 +226,7 @@ const HbStatisticsVerification = () => {
|
||||
state: {
|
||||
pagination: historyPagination,
|
||||
},
|
||||
onPaginationChange: (updater) => {
|
||||
setHistoryPagination((prev) =>
|
||||
typeof updater === "function" ? updater(prev) : updater,
|
||||
);
|
||||
},
|
||||
onPaginationChange: setHistoryPagination,
|
||||
meta: {
|
||||
onOutlierCellClick: (row: StAnlsMstResponse) => {
|
||||
setOutlierDialog({ open: true, anlsSn: row.anlsSn });
|
||||
|
||||
@@ -0,0 +1,520 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import type { ChartOptions } from "chart.js";
|
||||
import {
|
||||
CategoryScale,
|
||||
Chart as ChartJS,
|
||||
Legend,
|
||||
LinearScale,
|
||||
LineElement,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip as ChartTooltip,
|
||||
} from "chart.js";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { CodeApi, CodeSelect } from "@/entities/code";
|
||||
import type {
|
||||
StAnlsMstResponse,
|
||||
StAnlsMstSearchParams,
|
||||
StAnlsRsltResponse,
|
||||
} from "@/entities/statistics";
|
||||
import {
|
||||
imrDetailGridColumnInfo,
|
||||
imrVerificationHistoryColumns,
|
||||
} from "@/entities/statistics";
|
||||
import { analysisHistoryApi } from "@/entities/statistics/api/analysis-history-api";
|
||||
import { imrApi } from "@/entities/statistics/api/imr-api";
|
||||
import { stAnlsRsltApi } from "@/entities/statistics/api/statistics-analysis-result-api";
|
||||
import {
|
||||
Button,
|
||||
DataTable,
|
||||
DateRangePicker,
|
||||
Label,
|
||||
LoadingWithAnimation,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
ChartTooltip,
|
||||
Legend,
|
||||
);
|
||||
|
||||
type DateRangeValue = {
|
||||
from: Date;
|
||||
to?: Date;
|
||||
};
|
||||
|
||||
const EMPTY_DATA: never[] = [];
|
||||
|
||||
const ImrVerificationHistory = () => {
|
||||
// Area A Search States
|
||||
const [searchSido, setSearchSido] = useState<string>("");
|
||||
const [searchSigungu, setSearchSigungu] = useState<string>("");
|
||||
const [searchStatsCd, setSearchStatsCd] = useState<string>("");
|
||||
const [dateRange, setDateRange] = useState<DateRangeValue | undefined>({
|
||||
from: dayjs().subtract(1, "month").toDate(),
|
||||
to: dayjs().toDate(),
|
||||
});
|
||||
const [appliedSearchParams, setAppliedSearchParams] =
|
||||
useState<StAnlsMstSearchParams>({
|
||||
page: 0,
|
||||
size: 10,
|
||||
vrfcMthdCd: "IMR",
|
||||
sort: "anlsSn,desc",
|
||||
});
|
||||
|
||||
// Selected States
|
||||
const [selectedHistory, setSelectedHistory] =
|
||||
useState<StAnlsMstResponse | null>(null);
|
||||
const [selectedDetail, setSelectedDetail] =
|
||||
useState<StAnlsRsltResponse | null>(null);
|
||||
|
||||
// Area B Grid State
|
||||
const [gridRowData, setGridRowData] = useState<
|
||||
Record<string, string | number>[]
|
||||
>([]);
|
||||
|
||||
// Area A Pagination
|
||||
const [historyPagination, setHistoryPagination] = useState({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
// Codes for Filter
|
||||
const { data: lwpoCodes } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CODE, "LAESECD"],
|
||||
queryFn: () => CodeApi.getCode("LAESECD"),
|
||||
});
|
||||
|
||||
const sidoOptions = useMemo(() => {
|
||||
if (!lwpoCodes) return [];
|
||||
return lwpoCodes.filter(
|
||||
(c) => c.lwpoCd.startsWith("G3") && c.lwpoCd.length === 4,
|
||||
);
|
||||
}, [lwpoCodes]);
|
||||
|
||||
const sigunguOptions = useMemo(() => {
|
||||
if (!lwpoCodes || searchSido === "") return [];
|
||||
return lwpoCodes.filter(
|
||||
(c) => c.lwpoCd.startsWith(searchSido) && c.lwpoCd.length === 7,
|
||||
);
|
||||
}, [lwpoCodes, searchSido]);
|
||||
|
||||
// Area A Data Fetching
|
||||
const { data: historyData } = useQuery({
|
||||
queryKey: [
|
||||
ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST,
|
||||
appliedSearchParams,
|
||||
historyPagination,
|
||||
],
|
||||
queryFn: () =>
|
||||
analysisHistoryApi.getAnalysisHistoryList({
|
||||
...appliedSearchParams,
|
||||
page: historyPagination.pageIndex,
|
||||
size: historyPagination.pageSize,
|
||||
}),
|
||||
});
|
||||
|
||||
const historyTable = useReactTable({
|
||||
data: historyData?.content ?? EMPTY_DATA,
|
||||
columns: imrVerificationHistoryColumns,
|
||||
getRowId: (row) => String(row.anlsSn),
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: historyData?.totalPages ?? 0,
|
||||
state: {
|
||||
pagination: historyPagination,
|
||||
rowSelection: selectedHistory
|
||||
? { [selectedHistory.anlsSn]: true }
|
||||
: {},
|
||||
},
|
||||
onPaginationChange: setHistoryPagination,
|
||||
});
|
||||
|
||||
// Area B Data Fetching
|
||||
const { data: detailData, isLoading: isDetailLoading } = useQuery({
|
||||
queryKey: [
|
||||
ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT,
|
||||
selectedHistory?.anlsSn,
|
||||
],
|
||||
queryFn: () =>
|
||||
stAnlsRsltApi.getImrResultList(selectedHistory!.anlsSn, {
|
||||
page: 0,
|
||||
size: 100,
|
||||
sort: "rsltSn,asc",
|
||||
}),
|
||||
enabled: !!selectedHistory,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!detailData || !detailData.content) {
|
||||
setGridRowData([]);
|
||||
return;
|
||||
}
|
||||
const mapped = detailData.content.map((item) => ({
|
||||
...item,
|
||||
statsNm: selectedHistory?.vrfcTrgtNm ?? "-",
|
||||
}));
|
||||
setGridRowData(mapped as unknown as Record<string, string | number>[]);
|
||||
}, [detailData, selectedHistory]);
|
||||
|
||||
// Area C Data Fetching
|
||||
const { data: chartData, isLoading: isChartLoading } = useQuery({
|
||||
queryKey: [
|
||||
"imr-log",
|
||||
selectedHistory?.anlsSn,
|
||||
selectedDetail?.vrfcTrgtSn,
|
||||
selectedDetail?.admdstCd,
|
||||
],
|
||||
queryFn: () =>
|
||||
imrApi.getOutliersLog({
|
||||
anlsSn: selectedHistory!.anlsSn,
|
||||
vrfcTrgtSn: selectedDetail!.vrfcTrgtSn,
|
||||
admdstCd: selectedDetail!.admdstCd,
|
||||
}),
|
||||
enabled: !!selectedHistory && !!selectedDetail,
|
||||
});
|
||||
|
||||
const handleSearch = () => {
|
||||
const params: StAnlsMstSearchParams = {
|
||||
vrfcMthdCd: "IMR",
|
||||
sort: "anlsSn,desc",
|
||||
};
|
||||
if (searchSido !== "")
|
||||
params.sewerBzmnCd =
|
||||
searchSigungu !== "" ? searchSigungu : searchSido;
|
||||
if (searchStatsCd) params.statsCd = searchStatsCd;
|
||||
if (dateRange?.from)
|
||||
params.verifyDtStart = dayjs(dateRange.from).format("YYYY-MM-DD");
|
||||
if (dateRange?.to)
|
||||
params.verifyDtEnd = dayjs(dateRange.to).format("YYYY-MM-DD");
|
||||
|
||||
setAppliedSearchParams(params);
|
||||
setHistoryPagination((prev) => ({ ...prev, pageIndex: 0 }));
|
||||
setSelectedHistory(null);
|
||||
setSelectedDetail(null);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchSido("");
|
||||
setSearchSigungu("");
|
||||
setSearchStatsCd("");
|
||||
const defaultRange = {
|
||||
from: dayjs().subtract(1, "month").toDate(),
|
||||
to: dayjs().toDate(),
|
||||
};
|
||||
setDateRange(defaultRange);
|
||||
};
|
||||
|
||||
const chartOptions: ChartOptions<"line"> = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: "top" as const,
|
||||
labels: {
|
||||
font: {
|
||||
size: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: selectedDetail
|
||||
? `${selectedDetail.fcltNm} - I-MR 분석 차트`
|
||||
: "I-MR 분석 차트",
|
||||
font: {
|
||||
size: 16,
|
||||
weight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
title: {
|
||||
display: true,
|
||||
text: "수치",
|
||||
},
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "연도",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[selectedDetail],
|
||||
);
|
||||
|
||||
const lineChartData = useMemo(() => {
|
||||
if (!chartData || !chartData.history) return null;
|
||||
|
||||
const labels = chartData.history.map((h) => String(h.year));
|
||||
const actualData = chartData.history.map((h) => h.x);
|
||||
const uclData = chartData.history.map((h) => h.ucl);
|
||||
const lclData = chartData.history.map((h) => h.lcl);
|
||||
const predictedData = chartData.history.map((h) => h.predicted);
|
||||
|
||||
return {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "실측치 (X)",
|
||||
data: actualData,
|
||||
borderColor: "rgb(75, 192, 192)",
|
||||
backgroundColor: "rgba(75, 192, 192, 0.5)",
|
||||
tension: 0.1,
|
||||
pointRadius: 4,
|
||||
pointHoverRadius: 6,
|
||||
},
|
||||
{
|
||||
label: "상한선 (UCL)",
|
||||
data: uclData,
|
||||
borderColor: "rgb(255, 99, 132)",
|
||||
backgroundColor: "rgba(255, 99, 132, 0.2)",
|
||||
borderDash: [5, 5],
|
||||
pointStyle: "false",
|
||||
fill: false,
|
||||
},
|
||||
{
|
||||
label: "하한선 (LCL)",
|
||||
data: lclData,
|
||||
borderColor: "rgb(54, 162, 235)",
|
||||
backgroundColor: "rgba(54, 162, 235, 0.2)",
|
||||
borderDash: [5, 5],
|
||||
pointStyle: "false",
|
||||
fill: false,
|
||||
},
|
||||
{
|
||||
label: "예측치 (Predicted)",
|
||||
data: predictedData,
|
||||
borderColor: "rgb(153, 102, 255)",
|
||||
backgroundColor: "rgba(153, 102, 255, 0.5)",
|
||||
borderDash: [2, 2],
|
||||
pointStyle: "false",
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [chartData]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* 영역 A: 검증 이력 그리드 + 검색 필터 */}
|
||||
<div className="card card-border bg-white p-6">
|
||||
<div className="flex flex-wrap items-end gap-6 p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-[1.3rem] font-medium text-label">
|
||||
시도
|
||||
</Label>
|
||||
<Select
|
||||
value={searchSido}
|
||||
onValueChange={(val) => {
|
||||
setSearchSido(val);
|
||||
setSearchSigungu("");
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[14rem]">
|
||||
<SelectValue placeholder="시도 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{sidoOptions.map((opt) => (
|
||||
<SelectItem
|
||||
key={opt.lwpoCd}
|
||||
value={opt.lwpoCd}
|
||||
>
|
||||
{opt.lwpoCdNm}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-[1.3rem] font-medium text-label">
|
||||
시군구
|
||||
</Label>
|
||||
<Select
|
||||
value={searchSigungu}
|
||||
onValueChange={setSearchSigungu}
|
||||
disabled={searchSido === ""}
|
||||
>
|
||||
<SelectTrigger className="w-[14rem]">
|
||||
<SelectValue placeholder="시군구 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{sigunguOptions.map((opt) => (
|
||||
<SelectItem
|
||||
key={opt.lwpoCd}
|
||||
value={opt.lwpoCd}
|
||||
>
|
||||
{opt.lwpoCdNm}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-[1.3rem] font-medium text-label">
|
||||
검증일시
|
||||
</Label>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onValueChange={(val) =>
|
||||
setDateRange(
|
||||
val
|
||||
? { from: val.from!, to: val.to }
|
||||
: undefined,
|
||||
)
|
||||
}
|
||||
className="w-[20rem]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-[1.3rem] font-medium text-label">
|
||||
통계항목
|
||||
</Label>
|
||||
<CodeSelect
|
||||
upCd="STITEM"
|
||||
value={searchStatsCd}
|
||||
onValueChange={setSearchStatsCd}
|
||||
placeholder="통계항목 선택"
|
||||
className="w-[22rem]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ml-auto flex gap-2">
|
||||
<Button color="gray" onClick={handleReset}>
|
||||
초기화
|
||||
</Button>
|
||||
<Button onClick={handleSearch}>검색</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pb-4">
|
||||
<DataTable
|
||||
table={historyTable}
|
||||
variant="secondary"
|
||||
onRowClick={(row) => {
|
||||
setSelectedHistory(row);
|
||||
setSelectedDetail(null);
|
||||
}}
|
||||
viewPagination={true}
|
||||
className="[&_td]:!p-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
{/* 영역 B: 상세 데이터 그리드 */}
|
||||
<div className="card card-border bg-white p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="pl-2 text-[1.6rem] font-bold text-title">
|
||||
이상치 데이터
|
||||
</h2>
|
||||
<Button
|
||||
color="green"
|
||||
disabled={
|
||||
!detailData || detailData.content.length === 0
|
||||
}
|
||||
onClick={() => {
|
||||
if (selectedHistory) {
|
||||
stAnlsRsltApi.downloadExcel(
|
||||
selectedHistory.anlsSn,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
다운로드
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-2 h-[45rem]">
|
||||
{isDetailLoading ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
) : !selectedHistory || gridRowData.length === 0 ? (
|
||||
<div className="flex h-full w-full items-center justify-center text-[1.4rem] text-muted-foreground">
|
||||
{!selectedHistory
|
||||
? "항목을 선택해주세요."
|
||||
: "표시할 항목이 없습니다."}
|
||||
</div>
|
||||
) : (
|
||||
<DataGrid
|
||||
columnInfo={imrDetailGridColumnInfo}
|
||||
rowData={gridRowData}
|
||||
setRowData={setGridRowData}
|
||||
idFieldName="rsltSn"
|
||||
isExcelExport={false}
|
||||
isExcelImport={false}
|
||||
highlightedRowId={selectedDetail?.rsltSn}
|
||||
onRowClick={(e) => {
|
||||
if (!e.rowId) return;
|
||||
const row = detailData?.content?.find(
|
||||
(item) =>
|
||||
String(item.rsltSn) ===
|
||||
String(e.rowId),
|
||||
);
|
||||
if (row) setSelectedDetail(row);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 영역 C: Line Chart */}
|
||||
<div className="card card-border bg-white p-6">
|
||||
<h2 className="mb-4 text-[1.6rem] font-bold text-title">
|
||||
검증 결과 차트
|
||||
</h2>
|
||||
<div className="flex h-[45rem] items-center justify-center rounded-lg border bg-slate-50 p-4">
|
||||
{isChartLoading ? (
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
) : lineChartData ? (
|
||||
<Line options={chartOptions} data={lineChartData} />
|
||||
) : (
|
||||
<div className="text-[1.4rem] text-muted-foreground">
|
||||
상세 데이터를 선택하면 차트가 표시됩니다.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImrVerificationHistory;
|
||||
@@ -7,12 +7,12 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { CodeSelect } from "@/entities/code/ui/code-select";
|
||||
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
|
||||
import {
|
||||
hbArticleColumnInfo,
|
||||
imrArticleColumnInfo,
|
||||
validationArticleColumnInfo,
|
||||
} from "@/entities/statistics/model/verification-article-column";
|
||||
} from "@/entities/statistics";
|
||||
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
|
||||
import type {
|
||||
VerificationTargetManagementResponse,
|
||||
VerificationTargetManagementSearchParams,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import type { StAnlsDtlResponse } from "@/entities/statistics/model/statistics-analysis-detail-interface";
|
||||
import type { StAnlsDtlResponse } from "@/entities/statistics";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import Indicators from "./Indicators";
|
||||
|
||||
@@ -4,10 +4,10 @@ import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { CodeApi } from "@/entities/code/api/code-service";
|
||||
import type { StAnlsRsltResponse } from "@/entities/statistics";
|
||||
import { useStAnlsRsltList } from "@/entities/statistics/api/queries";
|
||||
import { stAnlsRsltApi } from "@/entities/statistics/api/statistics-analysis-result-api";
|
||||
import { regionalGridColumnInfo as columnInfo } from "@/entities/statistics/model/regional-grid-columns";
|
||||
import type { StAnlsRsltResponse } from "@/entities/statistics/model/statistics-analysis-result-interface";
|
||||
import {
|
||||
Button,
|
||||
LoadingWithAnimation,
|
||||
@@ -112,7 +112,7 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
|
||||
if (appliedFilter.status) {
|
||||
data = data.filter(
|
||||
(item) =>
|
||||
item.otlrYn.toLowerCase() === appliedFilter.status,
|
||||
item.otlrYnCd.toLowerCase() === appliedFilter.status,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
|
||||
|
||||
const mappedData = data.map((item) => ({
|
||||
...item,
|
||||
otlrYn: mapOtlrToLabel(item.otlrYn),
|
||||
otlrYn: mapOtlrToLabel(item.otlrYnCd),
|
||||
}));
|
||||
|
||||
setGridRowData(
|
||||
|
||||
@@ -19,11 +19,11 @@ import zoomPlugin from "chartjs-plugin-zoom";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { CodeApi } from "@/entities/code/api/code-service";
|
||||
import type { StAnlsRsltResponse } from "@/entities/statistics";
|
||||
import {
|
||||
useStAnlsChartData,
|
||||
useStAnlsRsltList,
|
||||
} from "@/entities/statistics/api/queries";
|
||||
import type { StAnlsRsltResponse } from "@/entities/statistics/model/statistics-analysis-result-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import type { AppliedFilter } from "./HbVerificationDetail";
|
||||
@@ -124,7 +124,7 @@ const ScatterChart: React.FC<ScatterChartProps> = ({
|
||||
|
||||
if (
|
||||
appliedFilter.status &&
|
||||
item.otlrYn.toLowerCase() !== appliedFilter.status
|
||||
item.otlrYnCd.toLowerCase() !== appliedFilter.status
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +135,7 @@ const ScatterChart: React.FC<ScatterChartProps> = ({
|
||||
item,
|
||||
};
|
||||
|
||||
if (item.otlrYn === "0") {
|
||||
if (item.otlrYnCd === "0") {
|
||||
normalPoints.push(pt);
|
||||
} else {
|
||||
outlierPoints.push(pt);
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { StAnlsDtlResponse } from "@/entities/statistics";
|
||||
import { getVerificationListColumns } from "@/entities/statistics";
|
||||
import { stAnlsDtlApi } from "@/entities/statistics/api/statistics-analysis-detail-api";
|
||||
import type { StAnlsDtlResponse } from "@/entities/statistics/model/statistics-analysis-detail-interface";
|
||||
import { getVerificationListColumns } from "@/entities/statistics/model/verification-list-columns";
|
||||
import { DataTable } from "@common/components";
|
||||
|
||||
interface VerificationListProps {
|
||||
|
||||
@@ -2,40 +2,39 @@ import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
AnalysisRequestPayload,
|
||||
AnalysisResponseDto,
|
||||
JobStatusDto,
|
||||
AnalysisResultDto,
|
||||
} from "../model/analysis-interface";
|
||||
AnalysisRequestPayload,
|
||||
AnalysisResponseDto,
|
||||
JobStatusDto,
|
||||
AnalysisResultDto,
|
||||
} from "../model/analysis/analysis-interface";
|
||||
|
||||
export class AnalysisApiService {
|
||||
public async analyze(
|
||||
payload: AnalysisRequestPayload,
|
||||
): Promise<AnalysisResponseDto> {
|
||||
const response = await client.post<AnalysisResponseDto>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/analyze`,
|
||||
payload,
|
||||
);
|
||||
public async analyze(
|
||||
payload: AnalysisRequestPayload,
|
||||
): Promise<AnalysisResponseDto> {
|
||||
const response = await client.post<AnalysisResponseDto>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/analyze`,
|
||||
payload,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getJobStatus(jobId: string): Promise<JobStatusDto> {
|
||||
const response = await client.get<JobStatusDto>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/status/${jobId}`,
|
||||
);
|
||||
public async getJobStatus(jobId: string): Promise<JobStatusDto> {
|
||||
const response = await client.get<JobStatusDto>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/status/${jobId}`,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getResult(jobId: string): Promise<AnalysisResultDto> {
|
||||
const response = await client.get<AnalysisResultDto>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/result/${jobId}`,
|
||||
);
|
||||
public async getResult(jobId: string): Promise<AnalysisResultDto> {
|
||||
const response = await client.get<AnalysisResultDto>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/result/${jobId}`,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const analysisApi = new AnalysisApiService();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
StAnlsMstListApiResponse,
|
||||
StAnlsMstSearchParams,
|
||||
StAnlsOutlierListResponse,
|
||||
} from "../model/analysis-history-interface";
|
||||
} from "../model/statistics-analysis/statistics-analysis-master-interface";
|
||||
|
||||
export class AnalysisHistoryApiService {
|
||||
public async getAnalysisHistoryList(
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
ImrListResponse,
|
||||
ImrLogRequest,
|
||||
ImrLogResponse,
|
||||
} from "../model/imr-interface";
|
||||
} from "../model/imr/imr-interface";
|
||||
|
||||
export class ImrApiService {
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageableParams } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
ChartRetDto,
|
||||
StAnlsRsltListApiResponse,
|
||||
StAnlsRsltSearchParams,
|
||||
} from "../model/statistics-analysis-result-interface";
|
||||
} from "../model/statistics-analysis/statistics-analysis-result-interface";
|
||||
|
||||
export class StAnlsRsltApiService {
|
||||
public async getResultList(
|
||||
@@ -23,7 +24,7 @@ export class StAnlsRsltApiService {
|
||||
|
||||
public async getImrResultList(
|
||||
anlsSn: number,
|
||||
params: { page?: number; size?: number },
|
||||
params: PageableParams,
|
||||
): Promise<StAnlsRsltListApiResponse> {
|
||||
const response = await client.get<StAnlsRsltListApiResponse>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT}/imr/rsltlist/${anlsSn}`,
|
||||
@@ -62,4 +63,3 @@ export class StAnlsRsltApiService {
|
||||
}
|
||||
|
||||
export const stAnlsRsltApi = new StAnlsRsltApiService();
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
export {
|
||||
type AnalysisHistoryListApiResponse,
|
||||
type AnalysisRequestPayload,
|
||||
type AnalysisResponseDto,
|
||||
type AnalysisResultDto,
|
||||
type AnalysisSummaryDto,
|
||||
type JobStatusDto,
|
||||
type OutlierRecordDto,
|
||||
} from "./model/analysis/analysis-interface";
|
||||
export { imrDetailGridColumnInfo } from "./model/imr/imr-detail-grid-column-info";
|
||||
export { imrDetailGridColumns } from "./model/imr/imr-detail-grid-columns";
|
||||
export {
|
||||
type ImrDetailRequest,
|
||||
type ImrDetailResponse,
|
||||
type ImrDetailResponseItem,
|
||||
type ImrDetectRequest,
|
||||
type ImrDetectResponse,
|
||||
type ImrListRequest,
|
||||
type ImrListResponse,
|
||||
type ImrLogDataParam,
|
||||
type ImrLogRequest,
|
||||
type ImrLogResponse,
|
||||
} from "./model/imr/imr-interface";
|
||||
export { imrVerificationHistoryColumns } from "./model/imr/imr-verification-history-columns";
|
||||
export {
|
||||
type StAnlsDtlListApiResponse,
|
||||
type StAnlsDtlResponse,
|
||||
type StAnlsDtlSearchParams,
|
||||
} from "./model/statistics-analysis/statistics-analysis-detail-interface";
|
||||
export {
|
||||
type StAnlsMstListApiResponse,
|
||||
type StAnlsMstResponse,
|
||||
type StAnlsMstResponseDto,
|
||||
type StAnlsMstSearchDto,
|
||||
type StAnlsMstSearchParams,
|
||||
type StAnlsOutlierDataResponse,
|
||||
type StAnlsOutlierDataResponseDto,
|
||||
type StAnlsOutlierListResponse,
|
||||
type StAnlsOutlierListResponseDto,
|
||||
} from "./model/statistics-analysis/statistics-analysis-master-interface";
|
||||
export {
|
||||
type ChartBoundaryPointDto,
|
||||
type ChartRetDto,
|
||||
type StAnlsRsltListApiResponse,
|
||||
type StAnlsRsltResponse,
|
||||
type StAnlsRsltSearchParams,
|
||||
} from "./model/statistics-analysis/statistics-analysis-result-interface";
|
||||
export { verificationHistoryColumns } from "./model/statistics-analysis/verification-history-columns";
|
||||
export { getVerificationListColumns } from "./model/statistics-analysis/verification-list-columns";
|
||||
export {
|
||||
hbArticleColumnInfo,
|
||||
imrArticleColumnInfo,
|
||||
validationArticleColumnInfo,
|
||||
} from "./model/verification-article/verification-article-column";
|
||||
export {
|
||||
type HBArticleItem,
|
||||
type IMRArticleItem,
|
||||
type ValidationArticleItem,
|
||||
} from "./model/verification-article/verification-article-interface";
|
||||
@@ -1,63 +0,0 @@
|
||||
export interface ImrDetectRequest {
|
||||
anlsSn: number;
|
||||
vrfcTrgtSn: number;
|
||||
statsYr: string;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export interface ImrDetectResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
jobId?: string;
|
||||
}
|
||||
|
||||
export interface ImrListRequest {
|
||||
anlsSn?: number;
|
||||
vrfcTrgtSn?: number;
|
||||
statsYr?: string;
|
||||
}
|
||||
|
||||
export interface ImrListResponse {
|
||||
anlsSn: number;
|
||||
vrfcTrgtSn: number;
|
||||
statsYr: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string;
|
||||
modelNm: string;
|
||||
anlsDt: string;
|
||||
}
|
||||
|
||||
export interface ImrDetailRequest {
|
||||
anlsSn: number;
|
||||
vrfcTrgtSn: number;
|
||||
admdstCd?: string;
|
||||
}
|
||||
|
||||
export interface ImrDetailResponse {
|
||||
anlsSn: number;
|
||||
vrfcTrgtSn: number;
|
||||
details: Array<{
|
||||
admdstCd: string;
|
||||
admdstNm: string;
|
||||
val: number;
|
||||
isOutlier: boolean;
|
||||
score: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ImrLogRequest {
|
||||
anlsSn: number;
|
||||
vrfcTrgtSn: number;
|
||||
admdstCd: string;
|
||||
}
|
||||
|
||||
export interface ImrLogResponse {
|
||||
anlsSn: number;
|
||||
vrfcTrgtSn: number;
|
||||
admdstCd: string;
|
||||
logs: Array<{
|
||||
timestamp: string;
|
||||
level: string;
|
||||
message: string;
|
||||
}>;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
|
||||
export const imrDetailGridColumnInfo: ColumnInfo = {
|
||||
columns: [
|
||||
{
|
||||
name: "sido",
|
||||
fieldName: "sido",
|
||||
header: { text: "시도" },
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
name: "gugun",
|
||||
fieldName: "gugun",
|
||||
header: { text: "구군" },
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
name: "admdstNm",
|
||||
fieldName: "admdstNm",
|
||||
header: { text: "행정구역" },
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: "admdstCd",
|
||||
fieldName: "admdstCd",
|
||||
header: { text: "시설코드" },
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: "fcltNm",
|
||||
fieldName: "fcltNm",
|
||||
header: { text: "시설명" },
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: "addr",
|
||||
fieldName: "addr",
|
||||
header: { text: "주소" },
|
||||
width: 200,
|
||||
styleName: "left-column",
|
||||
},
|
||||
{
|
||||
name: "pipeSeCd",
|
||||
fieldName: "pipeSeCd",
|
||||
header: { text: "관로구분" },
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
name: "vrfcTrgtSn",
|
||||
fieldName: "vrfcTrgtSn",
|
||||
header: { text: "항목번호" },
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
name: "statsNm",
|
||||
fieldName: "statsNm",
|
||||
header: { text: "항목명" },
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: "cryrVl",
|
||||
fieldName: "cryrVl",
|
||||
header: { text: "당해년도" },
|
||||
width: 100,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0",
|
||||
styleName: "right-column",
|
||||
},
|
||||
],
|
||||
columnLayout: [
|
||||
"sido",
|
||||
"gugun",
|
||||
"admdstNm",
|
||||
"admdstCd",
|
||||
"fcltNm",
|
||||
"addr",
|
||||
"pipeSeCd",
|
||||
"vrfcTrgtSn",
|
||||
"statsNm",
|
||||
"cryrVl",
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import type { StAnlsRsltResponse } from "../statistics-analysis/statistics-analysis-result-interface";
|
||||
|
||||
const columnHelper = createColumnHelper<StAnlsRsltResponse>();
|
||||
|
||||
export const imrDetailGridColumns = [
|
||||
columnHelper.accessor("sido", {
|
||||
header: "시도",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-2">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("gugun", {
|
||||
header: "구군",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-2">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("admdstNm", {
|
||||
header: "행정구역",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-2">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("admdstCd", {
|
||||
header: "시설코드",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-2">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("fcltNm", {
|
||||
header: "시설명",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-2">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("addr", {
|
||||
header: "주소",
|
||||
cell: (info) => (
|
||||
<div
|
||||
className="max-w-[150px] truncate px-2 py-2"
|
||||
title={info.getValue() ?? ""}
|
||||
>
|
||||
{info.getValue() ?? "-"}
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("pipeSeCd", {
|
||||
header: "관로구분",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-2">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("vrfcTrgtSn", {
|
||||
header: "항목번호",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-2">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "statsNm",
|
||||
header: "항목명",
|
||||
cell: (info) => {
|
||||
const statsNm = (info.table.options.meta as { statsNm?: string })
|
||||
?.statsNm;
|
||||
return <div className="px-2 py-2">{statsNm ?? "-"}</div>;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("cryrVl", {
|
||||
header: "당해년도",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-2 text-right">
|
||||
{info.getValue()?.toLocaleString() ?? "-"}
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 이상치(IMR) 탐지 실행 요청 DTO
|
||||
*/
|
||||
export interface ImrDetectRequest {
|
||||
userId?: string; // 사용자 ID
|
||||
sewerBzmnCd: string; // 하수도 사업자 코드
|
||||
statsCd: string; // 통계 탭/항목 코드
|
||||
statsYr: string; // 분석 기준 통계 연도
|
||||
}
|
||||
|
||||
/**
|
||||
* 이상치(IMR) 탐지 실행 응답 DTO
|
||||
*/
|
||||
export interface ImrDetectResponse {
|
||||
anlsSn: number; // 분석 테이블 마스터 일련번호
|
||||
jobId: string; // Celery/비동기 처리를 위한 고유 작업 ID
|
||||
totalOtlrCnt: number; // 탐지된 총 이상치 건수
|
||||
statusCode: string; // 처리 상태 코드 (COMPLETED, FAIL, ERROR 등)
|
||||
errorMsg?: string; // 에러 발생 시 상세 메시지
|
||||
processingTimeMs: number; // 총 처리 소요 시간 (밀리초)
|
||||
}
|
||||
|
||||
/**
|
||||
* 이상치 탐지 목록(마스터) 조회 요청 DTO
|
||||
*/
|
||||
export interface ImrListRequest {
|
||||
sewerBzmnCd?: string; // 하수도 사업자 코드
|
||||
statsCd?: string; // 통계 항목 코드
|
||||
statsYr?: string; // 통계 연도
|
||||
}
|
||||
|
||||
/**
|
||||
* 이상치 탐지 목록(마스터) 조회 응답 DTO
|
||||
*/
|
||||
export interface ImrListResponse {
|
||||
anlsSn: number; // 분석 마스터 일련번호
|
||||
anlsSttsCd: string; // 분석 상태 코드 (COMPLETED, FAIL, ERROR)
|
||||
nmlDataNocs: number; // 정상 판정 데이터 건수
|
||||
exclDataNocs: number; // 분석 제외 데이터 건수
|
||||
otlrDataNocs: number; // 이상치 판정 데이터 건수
|
||||
}
|
||||
|
||||
/**
|
||||
* 이상치 탐지 상세 조회 요청 DTO
|
||||
*/
|
||||
export interface ImrDetailRequest {
|
||||
anlsSn: number; // 분석 일련번호
|
||||
vrfcTrgtSn: number; // 검증 대상 지표 일련번호
|
||||
}
|
||||
|
||||
/**
|
||||
* 이상치 탐지 상세 조회 응답 아이템
|
||||
*/
|
||||
export interface ImrDetailResponseItem {
|
||||
anlsSn: number; // 분석 일련번호
|
||||
vrfcTrgtSn: number; // 검증 대상 지표 일련번호
|
||||
admdstCd: string; // 행정동(행정구역) 코드
|
||||
cryrVl: number; // 당해년도 원천 데이터 값
|
||||
otlrTrsfRt: number; // 이상치 판단 기준 (변이 척도)
|
||||
otlrYn: string; // 이상치 여부 플래그 (Y/N)
|
||||
}
|
||||
|
||||
/**
|
||||
* 이상치 탐지 상세 조회 응답 DTO
|
||||
*/
|
||||
export interface ImrDetailResponse {
|
||||
items: ImrDetailResponseItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 이상치 산출 로그 요청 DTO
|
||||
*/
|
||||
export interface ImrLogRequest {
|
||||
jobUuid?: string; // 작업 고유 식별자
|
||||
anlsSn: number; // 분석 일련번호
|
||||
vrfcTrgtSn: number; // 검증 대상 지표 일련번호
|
||||
admdstCd: string; // 행정동(행정구역) 코드
|
||||
}
|
||||
|
||||
/**
|
||||
* 이상치 로그 기록용 연도/시퀀스별 파라미터 DTO
|
||||
*/
|
||||
export interface ImrLogDataParam {
|
||||
year: number; // 대상 연도
|
||||
t: number; // 시간 시퀀스
|
||||
tDev: number; // 시점 간의 편차
|
||||
x: number; // 원천 데이터 값
|
||||
xDev: number; // 원천 데이터 값 편차
|
||||
prod: number; // t편차와 x편차의 곱
|
||||
tDevSq: number; // t편차의 제곱
|
||||
predicted: number; // 예측 정상치
|
||||
residuals: number; // 잔차
|
||||
mr: number; // 이동 범위(MR)
|
||||
ucl: number; // 관리 상한선
|
||||
lcl: number; // 관리 하한선
|
||||
judgment: string; // 최종 판정
|
||||
}
|
||||
|
||||
/**
|
||||
* 이상치 산출 상세 로그 응답 DTO
|
||||
*/
|
||||
export interface ImrLogResponse {
|
||||
sewerBzmnCd: string; // 하수도 사업자 코드
|
||||
colNm: string; // 영문 컬럼명
|
||||
vrfcTrgtNm: string; // 분석 대상 지표명
|
||||
admdstCd: string; // 행정동 코드
|
||||
regressionA: number; // 회귀식 기울기(a)
|
||||
regressionB: number; // 회귀식 절편(b)
|
||||
tMean: number; // 시퀀스 평균값
|
||||
xMean: number; // 원데이터 평균값
|
||||
prodMean: number; // 편차 곱의 평균
|
||||
tDevSqMean: number; // t편차 제곱의 평균
|
||||
mrMean: number; // 잔차 이동 범위 평균(MR)
|
||||
uc: number; // 상한 허용 폭(UCL)
|
||||
lc: number; // 하한 허용 폭(LCL)
|
||||
controlRange: number; // 관리 전체 폭(CL)
|
||||
confidenceLevel: string; // 신뢰 구간 표기
|
||||
history: ImrLogDataParam[]; // 연도별 상세 추적 기록
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
Progress,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@common/components";
|
||||
|
||||
import type { StAnlsMstResponse } from "../statistics-analysis/statistics-analysis-master-interface";
|
||||
|
||||
const columnHelper = createColumnHelper<StAnlsMstResponse>();
|
||||
|
||||
export const imrVerificationHistoryColumns = [
|
||||
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,
|
||||
errMsgCn: row.errMsgCn,
|
||||
}),
|
||||
{
|
||||
id: "anlsSttsNm",
|
||||
header: "진행 상태",
|
||||
cell: (info) => {
|
||||
const { anlsSttsCd, anlsSttsNm, anlsPrgrsRt, errMsgCn } =
|
||||
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>
|
||||
);
|
||||
}
|
||||
if (anlsSttsCd === "FAILED") {
|
||||
return (
|
||||
<div className="flex w-full items-center justify-center gap-1">
|
||||
{anlsSttsNm}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<i className="diveicon di-info-circle text-destructive" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-[64rem]">
|
||||
<p className="p-1 text-[1.2rem] leading-6">
|
||||
{errMsgCn}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return anlsSttsNm;
|
||||
},
|
||||
},
|
||||
),
|
||||
columnHelper.accessor("anlsEndDt", {
|
||||
header: "종료 일시",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("statsYr", {
|
||||
header: "검증년도",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-4 text-center">
|
||||
{info.getValue() ?? "-"}
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("sewerBzmnCd", {
|
||||
header: "하수도사업자",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("vrfcTrgtNm", {
|
||||
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 text-right">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("exclDataNocs", {
|
||||
header: "제외건수",
|
||||
cell: (info) => (
|
||||
<div className="px-2 py-4 text-right">{info.getValue() ?? "-"}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("otlrDataNocs", {
|
||||
header: "이상건수",
|
||||
cell: (info) => {
|
||||
const value = info.getValue() ?? 0;
|
||||
return <div className="px-2 py-4 text-right">{value}</div>;
|
||||
},
|
||||
}),
|
||||
];
|
||||
+1
-1
@@ -47,7 +47,7 @@ export interface StAnlsMstResponseDto {
|
||||
/**
|
||||
* 분석 현황 마스터 검색 DTO
|
||||
*/
|
||||
export interface StAnlsMstSearchDto extends PageableParams {
|
||||
export interface StAnlsMstSearchDto extends PageableParams {
|
||||
/** 분석상태코드 (COMPLETED, FAILED, QUEUED, PROCESSING) */
|
||||
anlsSttsCd?: string;
|
||||
/** 작업UUID */
|
||||
+2
-2
@@ -20,7 +20,7 @@ export interface StAnlsRsltResponse {
|
||||
chgRt: number | null; // 변경율 (r_i)
|
||||
trsfRt: number | null; // 변환 비율 (s_i)
|
||||
otlrTrsfRt: number | null; // 이상치 변환 비율 (E_i)
|
||||
otlrYn: string; // 이상치 여부 (0: 정상, 1: 이상치, 2: 제외)
|
||||
otlrYnCd: string; // 이상치 여부 (0: 정상, 1: 이상치, 2: 제외)
|
||||
}
|
||||
|
||||
export interface StAnlsRsltSearchParams extends PageableParams {
|
||||
@@ -29,7 +29,7 @@ export interface StAnlsRsltSearchParams extends PageableParams {
|
||||
admdstCd?: string; // 행정구역 코드
|
||||
admdstNm?: string; // 행정구역 명칭
|
||||
fcltNm?: string; // 시설명
|
||||
otlrYn?: string; // 이상치 여부 (y/n)
|
||||
otlrYnCd?: string; // 이상치 여부 (0: 정상, 1: 이상치, 2: 제외)
|
||||
}
|
||||
|
||||
export type StAnlsRsltListApiResponse = PageableList<StAnlsRsltResponse>;
|
||||
+1
-1
@@ -7,7 +7,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@common/components";
|
||||
|
||||
import type { StAnlsMstResponse } from "./analysis-history-interface";
|
||||
import type { StAnlsMstResponse } from "./statistics-analysis-master-interface";
|
||||
|
||||
const columnHelper = createColumnHelper<StAnlsMstResponse>();
|
||||
|
||||
+2
-1
@@ -2,9 +2,10 @@ import React from "react";
|
||||
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
import type { StAnlsDtlResponse } from "@/entities/statistics/model/statistics-analysis-detail-interface";
|
||||
import { Checkbox } from "@common/components";
|
||||
|
||||
import type { StAnlsDtlResponse } from "./statistics-analysis-detail-interface";
|
||||
|
||||
interface GetVerificationListColumnsParams {
|
||||
rowData: StAnlsDtlResponse[];
|
||||
checkedSns: string[];
|
||||
@@ -1,150 +0,0 @@
|
||||
import { fakerKO as faker } from "@faker-js/faker";
|
||||
import type {
|
||||
HBArticleItem,
|
||||
IMRArticleItem,
|
||||
ValidationArticleItem,
|
||||
} from "./verification-article-interface";
|
||||
|
||||
const HB_STATS_ITEMS = [
|
||||
"하수도 보급률",
|
||||
"하수관로",
|
||||
"하수처리시설 및 방류량보고",
|
||||
"펌프장",
|
||||
"공공하수처리시설",
|
||||
"분뇨처리시설",
|
||||
"개인하수처리시설",
|
||||
"하수도재정",
|
||||
"물 재이용",
|
||||
];
|
||||
|
||||
const HB_VERIFICATION_ITEMS = [
|
||||
"행정구역 보급률",
|
||||
"유역환경청별 보급률",
|
||||
"관로연장",
|
||||
"처리용량",
|
||||
"방류량",
|
||||
"펌프용량",
|
||||
];
|
||||
|
||||
const IMR_STATS_ITEMS = [
|
||||
"하수도 보급률",
|
||||
"하수관로",
|
||||
"하수처리시설 및 방류량보고",
|
||||
"펌프장",
|
||||
"공공하수처리시설",
|
||||
"분뇨처리시설",
|
||||
"개인하수처리시설",
|
||||
"하수도재정",
|
||||
"물 재이용",
|
||||
];
|
||||
|
||||
const VALIDATION_STATS_ITEMS = [
|
||||
"하수관로 유지관리",
|
||||
"하수저류시설 및 빗물펌프장",
|
||||
"공공하수처리시설",
|
||||
"분뇨처리시설",
|
||||
"개인하수처리시설",
|
||||
"하수도재정",
|
||||
];
|
||||
|
||||
const VERIFICATION_NAMES = [
|
||||
"제정별 사업비",
|
||||
"관로길이",
|
||||
"공란검증",
|
||||
"범위검증",
|
||||
"논리검증",
|
||||
"유지보수",
|
||||
];
|
||||
|
||||
const TABLE_NAMES = [
|
||||
"tb_stats_drng_sys",
|
||||
"tb_stats_ppline",
|
||||
"tb_stats_fclt",
|
||||
"tb_stats_pump_stt",
|
||||
"tb_stats_sewg_trtm",
|
||||
];
|
||||
|
||||
const COLUMN_NAMES = [
|
||||
"drng_sys_rate",
|
||||
"ppline_len",
|
||||
"fclt_cpct",
|
||||
"pump_cpct",
|
||||
"trtm_qty",
|
||||
"popl_cnt",
|
||||
];
|
||||
|
||||
const BASIC_RULES = [
|
||||
"값 > 0",
|
||||
"값 >= 0",
|
||||
"값 != NULL",
|
||||
"범위: 0-100",
|
||||
"A > B",
|
||||
];
|
||||
|
||||
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}`;
|
||||
}
|
||||
|
||||
/** H-B 탭 목데이터 생성 */
|
||||
export function createHBArticleMock(count = 10): HBArticleItem[] {
|
||||
faker.seed(100);
|
||||
return Array.from({ length: count }, (_, _index) => ({
|
||||
statsItmNm: faker.helpers.arrayElement(HB_STATS_ITEMS),
|
||||
vrfcItmNm: faker.helpers.arrayElement(HB_VERIFICATION_ITEMS),
|
||||
tblNm: faker.helpers.arrayElement(TABLE_NAMES),
|
||||
colNm: faker.helpers.arrayElement(COLUMN_NAMES),
|
||||
percentileVal: faker.number.float({
|
||||
min: 0,
|
||||
max: 100,
|
||||
fractionDigits: 2,
|
||||
}),
|
||||
aVal: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }),
|
||||
cVal: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }),
|
||||
regNm: faker.person.fullName(),
|
||||
regDt: formatDateTime(faker.date.recent({ days: 90 })),
|
||||
}));
|
||||
}
|
||||
|
||||
/** I-MR 탭 목데이터 생성 */
|
||||
export function createIMRArticleMock(count = 10): IMRArticleItem[] {
|
||||
faker.seed(200);
|
||||
return Array.from({ length: count }, (_, _index) => ({
|
||||
statsItmNm: faker.helpers.arrayElement(IMR_STATS_ITEMS),
|
||||
vrfcNm: faker.helpers.arrayElement(VERIFICATION_NAMES),
|
||||
tblNm: faker.helpers.arrayElement(TABLE_NAMES),
|
||||
colNm: faker.helpers.arrayElement(COLUMN_NAMES),
|
||||
regNm: faker.person.fullName(),
|
||||
regDt: formatDateTime(faker.date.recent({ days: 90 })),
|
||||
}));
|
||||
}
|
||||
|
||||
/** Validation 탭 목데이터 생성 */
|
||||
export function createValidationArticleMock(
|
||||
count = 6,
|
||||
): ValidationArticleItem[] {
|
||||
faker.seed(300);
|
||||
return Array.from({ length: count }, (_, index) => ({
|
||||
no: `${index + 1}`,
|
||||
statsItmNm: faker.helpers.arrayElement(VALIDATION_STATS_ITEMS),
|
||||
vrfcNm: faker.helpers.arrayElement(VERIFICATION_NAMES),
|
||||
tblNm: faker.helpers.arrayElement(TABLE_NAMES),
|
||||
colNm: faker.helpers.arrayElement(COLUMN_NAMES),
|
||||
cmpTblNm: faker.helpers.arrayElement(TABLE_NAMES),
|
||||
cmpColNm: faker.helpers.arrayElement(COLUMN_NAMES),
|
||||
bscRul: faker.helpers.arrayElement(BASIC_RULES),
|
||||
}));
|
||||
}
|
||||
|
||||
/** H-B 탭 기본 목데이터 */
|
||||
export const hbArticleMockData = createHBArticleMock(10);
|
||||
|
||||
/** I-MR 탭 기본 목데이터 */
|
||||
export const imrArticleMockData = createIMRArticleMock(10);
|
||||
|
||||
/** Validation 탭 기본 목데이터 */
|
||||
export const validationArticleMockData = createValidationArticleMock(6);
|
||||
Reference in New Issue
Block a user