[st] feat: 통계 검증 상세 자료 다운로드 작업 및 QA 반영

This commit is contained in:
2026-03-11 17:45:16 +09:00
parent 57e8e4c8d6
commit dabdf5739a
9 changed files with 126 additions and 31 deletions
@@ -180,7 +180,8 @@ const HbStatisticsVerification = () => {
const params: StAnlsMstSearchParams = {
page: historyPagination.pageIndex,
size: historyPagination.pageSize,
sort: "anlsSn,desc",
sort: "anlsSn,desc",
vrfcMthdCd: "HB",
};
if (appliedHistoryDateRange?.from) {
@@ -1,11 +1,22 @@
"use client";
import DataGrid from "@/components/module/DataGrid";
import { useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
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";
import type {
VerificationTargetManagementResponse,
VerificationTargetManagementSearchParams,
} from "@/entities/statistics/model/verification-target-management-interface";
import {
Button,
Input,
@@ -15,17 +26,10 @@ import {
TabsList,
TabsTrigger,
} from "@common/components";
import { CodeSelect } from "@/entities/code/ui/code-select";
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { ENDPOINT } from "@/shared/config";
import type {
VerificationTargetManagementSearchParams,
VerificationTargetManagementResponse,
} from "@/entities/statistics/model/verification-target-management-interface";
import { VerificationTargetDialog } from "./VerificationTargetDialog";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import DataGrid from "@/components/module/DataGrid";
const TAB_CONFIG = {
HB: { value: "HB", label: "H-B", columnInfo: hbArticleColumnInfo },
@@ -1,5 +1,6 @@
import React, { useState } from "react";
import type { StAnlsDtlResponse } from "@/entities/statistics/model/statistics-analysis-detail-interface";
import { Button } from "@common/components";
import Indicators from "./Indicators";
@@ -20,6 +21,18 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
null,
);
const [selectedSido, setSelectedSido] = useState<string | null>(null);
const [selectedTargetInfo, setSelectedTargetInfo] = useState<{
statsNm: string;
vrfcTrgtNm: string;
} | null>(null);
const handleTargetSelect = (row: StAnlsDtlResponse) => {
setSelectedVrfcTrgtSn(String(row.vrfcTrgtSn));
setSelectedTargetInfo({
statsNm: row.statsNm,
vrfcTrgtNm: row.vrfcTrgtNm,
});
};
return (
<div className="flex flex-col">
@@ -44,7 +57,7 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
<VerificationList
anlsSn={anlsSn}
selectedVrfcTrgtSn={selectedVrfcTrgtSn}
onSelect={setSelectedVrfcTrgtSn}
onSelect={handleTargetSelect}
/>
</div>
@@ -57,6 +70,10 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
<RegionalGrid
anlsSn={anlsSn}
vrfcTrgtSn={selectedVrfcTrgtSn}
statsNm={selectedTargetInfo?.statsNm || ""}
vrfcTrgtNm={
selectedTargetInfo?.vrfcTrgtNm || ""
}
selectedSido={selectedSido}
onSidoSelect={setSelectedSido}
/>
@@ -23,6 +23,8 @@ import type { ColumnInfo } from "@/components/type/DataGrid.type";
interface RegionalGridProps {
anlsSn: number;
vrfcTrgtSn: string | null;
statsNm: string;
vrfcTrgtNm: string;
selectedSido: string | null;
onSidoSelect: (sido: string | null) => void;
}
@@ -36,6 +38,8 @@ interface AppliedFilter {
const RegionalGrid: React.FC<RegionalGridProps> = ({
anlsSn,
vrfcTrgtSn,
statsNm,
vrfcTrgtNm,
selectedSido,
onSidoSelect,
}) => {
@@ -48,6 +52,7 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
gugun: "",
status: "",
});
const [isDownloading, setIsDownloading] = useState<boolean>(false);
const [gridRowData, setGridRowData] = useState<
Record<string, string | number>[]
@@ -256,6 +261,31 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
setShowAllOnEmptyFilter(true);
};
const handleDownload = async () => {
if (!vrfcTrgtSn) return;
setIsDownloading(true);
try {
const blob = await stAnlsRsltApi.downloadExcel(
anlsSn,
Number(vrfcTrgtSn),
);
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `분석결과_${statsNm}-${vrfcTrgtNm}.xlsx`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error("Failed to download excel", error);
} finally {
setIsDownloading(false);
}
};
return (
<div className="flex flex-col gap-2">
<div className="flex justify-between">
@@ -310,8 +340,20 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
초기화
</Button>
<Button onClick={handleSearch}>조회</Button>
<Button color="green" onClick={() => {}}>
다운로드
<Button
color="green"
onClick={handleDownload}
disabled={!vrfcTrgtSn || isDownloading}
>
{isDownloading ? (
<div className="flex w-[4.8rem] items-center justify-center">
<div className="relative size-[2rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
) : (
"다운로드"
)}
</Button>
</div>
</div>
@@ -6,6 +6,7 @@ import {
Chart as ChartJS,
Legend,
LinearScale,
LineController,
LineElement,
PointElement,
ScatterController,
@@ -21,11 +22,12 @@ import { LoadingWithAnimation } from "@common/components";
ChartJS.register(
LinearScale,
PointElement,
LineController,
LineElement,
Tooltip,
Legend,
PointElement,
ScatterController,
Legend,
Tooltip,
zoomPlugin,
);
@@ -16,7 +16,7 @@ import { DataTable } from "@common/components";
interface VerificationListProps {
anlsSn: number;
selectedVrfcTrgtSn: string | null;
onSelect: (vrfcTrgtSn: string) => void;
onSelect: (row: StAnlsDtlResponse) => void;
}
const VerificationList: React.FC<VerificationListProps> = ({
@@ -101,7 +101,9 @@ const VerificationList: React.FC<VerificationListProps> = ({
data: rowData,
columns,
state: {
rowSelection: selectedVrfcTrgtSn ? { [selectedVrfcTrgtSn]: true } : {},
rowSelection: selectedVrfcTrgtSn
? { [selectedVrfcTrgtSn]: true }
: {},
},
enableMultiRowSelection: false,
getRowId: (row) => String(row.vrfcTrgtSn),
@@ -109,13 +111,13 @@ const VerificationList: React.FC<VerificationListProps> = ({
});
const handleRowClick = (row: StAnlsDtlResponse) => {
onSelect(String(row.vrfcTrgtSn));
onSelect(row);
};
useEffect(() => {
if (!selectedVrfcTrgtSn && rowData.length > 0) {
const firstTargetSn = rowData[0]?.vrfcTrgtSn;
if (firstTargetSn) onSelect(String(firstTargetSn));
const firstRow = rowData[0];
if (firstRow) onSelect(firstRow);
}
}, [selectedVrfcTrgtSn, rowData, onSelect]);
@@ -132,13 +134,15 @@ const VerificationList: React.FC<VerificationListProps> = ({
</div>
)}
{!isLoading && rowData.length > 0 && (
<DataTable
table={table}
variant="secondary"
onRowClick={handleRowClick}
viewPagination={false}
className="whitespace-nowrap text-[1.2rem] [&_td]:!px-2 [&_th]:!px-2"
/>
<div className="rounded-md border">
<DataTable
table={table}
variant="secondary"
onRowClick={handleRowClick}
viewPagination={false}
className="whitespace-nowrap text-[1.2rem] [&_td]:!px-2 [&_th]:!px-2"
/>
</div>
)}
</div>
);
@@ -31,6 +31,20 @@ export class StAnlsRsltApiService {
return response.data;
}
public async downloadExcel(
anlsSn: number,
vrfcTrgtSn: number,
): Promise<Blob> {
const response = await client.get<Blob>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT}/excel-down/${anlsSn}/${vrfcTrgtSn}`,
{
responseType: "blob",
},
);
return response.data;
}
}
export const stAnlsRsltApi = new StAnlsRsltApiService();
@@ -21,6 +21,7 @@ export interface StAnlsMstSearchParams extends PageableParams {
jobUuid?: string;
verifyDtStart?: string;
verifyDtEnd?: string;
vrfcMthdCd?: string;
}
export interface StAnlsOutlierDataResponse {
@@ -1,5 +1,5 @@
import { ValueType } from "realgrid";
import type { ColumnInfo } from "@rsone/components";
import { ValueType } from "realgrid";
export const hbArticleColumnInfo: ColumnInfo = {
columns: [
@@ -31,6 +31,15 @@ export const hbArticleColumnInfo: ColumnInfo = {
editable: false,
sortdisable: false,
},
{
name: "ipcrCntVl",
fieldName: "ipcrCntVl",
header: { text: "μ" },
editable: false,
sortdisable: false,
valueType: ValueType.NUMBER,
dataFormat: "#,##0.##",
},
{
name: "pctlCrtrVl",
fieldName: "pctlCrtrVl",
@@ -78,6 +87,7 @@ export const hbArticleColumnInfo: ColumnInfo = {
"vrfcTrgtNm",
"tblNm",
"colNm",
"ipcrCntVl",
"pctlCrtrVl",
"scopCntCntVl",
"thrsCntVl",