[st] feat: 통계 검증 상세 테이블 체크박스 기능 추가

This commit is contained in:
2026-03-17 14:51:43 +09:00
parent 414a69c6ad
commit ee2301470a
5 changed files with 210 additions and 156 deletions
@@ -26,6 +26,7 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
const [selectedVrfcTrgtSn, setSelectedVrfcTrgtSn] = useState<string | null>(
null,
);
const [checkedSns, setCheckedSns] = useState<string[]>([]);
const [appliedFilter, setAppliedFilter] = useState<AppliedFilter>({
sido: "",
gugun: "",
@@ -75,6 +76,8 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
anlsSn={anlsSn}
selectedVrfcTrgtSn={selectedVrfcTrgtSn}
onSelect={handleTargetSelect}
checkedSns={checkedSns}
onCheck={setCheckedSns}
/>
</div>
@@ -1,12 +1,12 @@
import React, { useEffect, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { ValueType } from "realgrid";
import { ENDPOINT } from "@/shared/config";
import { CodeApi } from "@/entities/code/api/code-service";
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,
@@ -21,7 +21,6 @@ import {
import type { AppliedFilter } from "./HbVerificationDetail";
import DataGrid from "@/components/module/DataGrid";
import type { ColumnInfo } from "@/components/type/DataGrid.type";
interface RegionalGridProps {
anlsSn: number;
@@ -148,89 +147,6 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
gugunOptions,
]);
const columnInfo: ColumnInfo = useMemo(
() => ({
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: "prvyrVl",
fieldName: "prvyrVl",
header: { text: "이전년도" },
width: 80,
valueType: ValueType.NUMBER,
dataFormat: "#,##0",
},
{
name: "cryrVl",
fieldName: "cryrVl",
header: { text: "당해년도" },
width: 80,
valueType: ValueType.NUMBER,
dataFormat: "#,##0",
},
{
name: "chgRt",
fieldName: "chgRt",
header: { text: "R" },
width: 60,
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
},
{
name: "trsfRt",
fieldName: "trsfRt",
header: { text: "S" },
width: 60,
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
},
{
name: "otlrTrsfRt",
fieldName: "otlrTrsfRt",
header: { text: "E" },
width: 60,
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
},
{
name: "otlrYn",
fieldName: "otlrYn",
header: { text: "상태" },
width: 60,
},
],
columnLayout: [
"sido",
"gugun",
"admdstNm",
"prvyrVl",
"cryrVl",
"chgRt",
"trsfRt",
"otlrTrsfRt",
"otlrYn",
],
}),
[],
);
const handleSidoChange = (val: string) => {
setSelectedSido(val);
setSelectedGugun("");
@@ -2,44 +2,31 @@ import React, { useEffect, useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import {
type ColumnDef,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import { ENDPOINT } from "@/shared/config";
import { stAnlsDtlApi } from "@/entities/statistics/api/statistics-analysis-detail-api";
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
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 {
anlsSn: number;
selectedVrfcTrgtSn: string | null;
onSelect: (row: StAnlsDtlResponse) => void;
checkedSns: string[];
onCheck: (checkedSns: string[], rows: StAnlsDtlResponse[]) => void;
}
const VerificationList: React.FC<VerificationListProps> = ({
anlsSn,
selectedVrfcTrgtSn,
onSelect,
checkedSns,
onCheck,
}) => {
const { data: verificationTargetCodes } = useQuery({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG, "codes"],
queryFn: () =>
verificationTargetManagementApi.getVerificationTargetCodes(),
});
const targetCodeMap = useMemo(() => {
const map = new Map<string, string>();
if (verificationTargetCodes) {
verificationTargetCodes.forEach((item) => {
map.set(item.vrfcTrgtSn, item.vrfcTrgtNm);
});
}
return map;
}, [verificationTargetCodes]);
const { data: detailData, isLoading } = useQuery({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL, "list", anlsSn],
queryFn: () =>
@@ -51,50 +38,9 @@ const VerificationList: React.FC<VerificationListProps> = ({
return (detailData?.content ?? []) as StAnlsDtlResponse[];
}, [detailData]);
const columns: ColumnDef<StAnlsDtlResponse>[] = useMemo(
() => [
{
header: "통계 년도",
accessorKey: "statsYr",
},
{
header: "통계 항목",
accessorKey: "statsNm",
},
{
header: "검증 항목",
accessorKey: "vrfcTrgtNm",
},
{
header: "μ",
accessorKey: "ipcrCntVl",
},
{
header: "Percentile",
accessorKey: "pctlCrtrVl",
},
{
header: "A",
accessorKey: "scopAjmtCntVl",
},
{
header: "C",
accessorKey: "thrsCntVl",
},
{
header: "정상건수",
accessorKey: "nmlDataNocs",
},
{
header: "제외건수",
accessorKey: "exclDataNocs",
},
{
header: "이상건수",
accessorKey: "otlrDataNocs",
},
],
[targetCodeMap],
const columns = useMemo(
() => getVerificationListColumns({ rowData, checkedSns, onCheck }),
[rowData, checkedSns, onCheck],
);
const table = useReactTable({
@@ -134,15 +80,13 @@ const VerificationList: React.FC<VerificationListProps> = ({
</div>
)}
{!isLoading && rowData.length > 0 && (
<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>
<DataTable
table={table}
variant="secondary"
onRowClick={handleRowClick}
viewPagination={false}
className="whitespace-nowrap text-[1.2rem] [&_td]:!px-2 [&_th]:!px-2"
/>
)}
</div>
);
@@ -0,0 +1,83 @@
import { ValueType } from "realgrid";
import type { ColumnInfo } from "@/components/type/DataGrid.type";
export const regionalGridColumnInfo: 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: "prvyrVl",
fieldName: "prvyrVl",
header: { text: "이전년도" },
width: 80,
valueType: ValueType.NUMBER,
dataFormat: "#,##0",
},
{
name: "cryrVl",
fieldName: "cryrVl",
header: { text: "당해년도" },
width: 80,
valueType: ValueType.NUMBER,
dataFormat: "#,##0",
},
{
name: "chgRt",
fieldName: "chgRt",
header: { text: "R" },
width: 60,
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
},
{
name: "trsfRt",
fieldName: "trsfRt",
header: { text: "S" },
width: 60,
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
},
{
name: "otlrTrsfRt",
fieldName: "otlrTrsfRt",
header: { text: "E" },
width: 60,
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
},
{
name: "otlrYn",
fieldName: "otlrYn",
header: { text: "상태" },
width: 60,
},
],
columnLayout: [
"sido",
"gugun",
"admdstNm",
"prvyrVl",
"cryrVl",
"chgRt",
"trsfRt",
"otlrTrsfRt",
"otlrYn",
],
};
@@ -0,0 +1,108 @@
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";
interface GetVerificationListColumnsParams {
rowData: StAnlsDtlResponse[];
checkedSns: string[];
onCheck: (checkedSns: string[], rows: StAnlsDtlResponse[]) => void;
}
export const getVerificationListColumns = ({
rowData,
checkedSns,
onCheck,
}: GetVerificationListColumnsParams): ColumnDef<StAnlsDtlResponse>[] => [
{
id: "checkbox",
header: () => {
const isAllChecked =
rowData.length > 0 && checkedSns.length === rowData.length;
return (
<div className="flex items-center justify-center">
<Checkbox
checked={isAllChecked}
onCheckedChange={(value: boolean) => {
if (value) {
onCheck(
rowData.map((r) => String(r.vrfcTrgtSn)),
rowData,
);
} else {
onCheck([], []);
}
}}
aria-label="Select all rows"
/>
</div>
);
},
cell: ({ row }) => {
const rowId = String(row.original.vrfcTrgtSn);
const isChecked = checkedSns.includes(rowId);
return (
<div className="flex items-center justify-center">
<Checkbox
checked={isChecked}
onCheckedChange={(value: boolean) => {
const newCheckedSns = value
? [...checkedSns, rowId]
: checkedSns.filter((id) => id !== rowId);
const newCheckedRows = rowData.filter((r) =>
newCheckedSns.includes(String(r.vrfcTrgtSn)),
);
onCheck(newCheckedSns, newCheckedRows);
}}
onClick={(e: React.MouseEvent) => e.stopPropagation()}
aria-label={`Select row ${rowId}`}
/>
</div>
);
},
enableSorting: false,
enableHiding: false,
},
{
header: "통계 년도",
accessorKey: "statsYr",
},
{
header: "통계 항목",
accessorKey: "statsNm",
},
{
header: "검증 항목",
accessorKey: "vrfcTrgtNm",
},
{
header: "μ",
accessorKey: "ipcrCntVl",
},
{
header: "Percentile",
accessorKey: "pctlCrtrVl",
},
{
header: "A",
accessorKey: "scopAjmtCntVl",
},
{
header: "C",
accessorKey: "thrsCntVl",
},
{
header: "정상건수",
accessorKey: "nmlDataNocs",
},
{
header: "제외건수",
accessorKey: "exclDataNocs",
},
{
header: "이상건수",
accessorKey: "otlrDataNocs",
},
];