From 4a38adb20ef811d43bf12b09631de36d5f85cb1d Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 8 May 2026 15:35:36 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[st]=20feat:=20HB=20=ED=86=B5=EA=B3=84=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../verification/HbStatisticsVerification.tsx | 39 ++-- .../verification/ImrVerificationHistory.tsx | 22 +- .../detail/HbVerificationDetail.tsx | 64 +++++- .../verification/detail/VerificationList.tsx | 8 +- .../st/src/entities/statistics/api/queries.ts | 18 +- .../api/statistics-analysis-detail-api.ts | 36 ---- .../api/statistics-analysis-detail-service.ts | 51 +++++ .../api/statistics-analysis-master-api.ts | 69 ------ .../api/statistics-analysis-master-service.ts | 71 ++++++ apps/st/src/entities/statistics/index.ts | 9 + .../statistics-analysis-detail-interface.ts | 12 ++ .../statistics-analysis-master-interface.ts | 5 +- .../verification-history-columns.tsx | 6 + .../verification-list-columns.tsx | 204 ++++++++++-------- .../src/shared/lib/use-mutation-wrapper.tsx | 41 ++-- 15 files changed, 396 insertions(+), 259 deletions(-) delete mode 100644 apps/st/src/entities/statistics/api/statistics-analysis-detail-api.ts create mode 100644 apps/st/src/entities/statistics/api/statistics-analysis-detail-service.ts delete mode 100644 apps/st/src/entities/statistics/api/statistics-analysis-master-api.ts create mode 100644 apps/st/src/entities/statistics/api/statistics-analysis-master-service.ts diff --git a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx index 722fd3eca..2206b5d3b 100644 --- a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx +++ b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx @@ -20,18 +20,18 @@ import { ENDPOINT } from "@/shared/config"; import { useAlertStore } from "@/shared/lib"; import type { ApiErrorResponse } from "@/shared/model"; import { CodeSelect } from "@/entities/code"; -import type { - AnalysisRequestPayload, - AnalysisResponseDto, - StAnlsMstResponse, - StAnlsMstSearchParams, - StAnlsOutlierListResponse, +import { + analysisApi, + type AnalysisRequestPayload, + type AnalysisResponseDto, + type StAnlsMstResponse, + type StAnlsMstSearchParams, + type StAnlsOutlierListResponse, + statisticsAnalysisMasterService, + verificationHistoryColumns, + type VerificationTargetCodeResponse, + verificationTargetManagementApi, } from "@/entities/statistics"; -import { verificationHistoryColumns } from "@/entities/statistics"; -import { analysisApi } from "@/entities/statistics/api/analysis-api"; -import { stAnlsMstApi } from "@/entities/statistics/api/statistics-analysis-master-api"; -import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service"; -import type { VerificationTargetCodeResponse } from "@/entities/statistics/model/verification-target-management-interface"; import { Button, Checkbox, @@ -102,7 +102,7 @@ function toNumber(value: string): number { return Number.isNaN(parsed) ? 0 : parsed; } -const HbStatisticsVerification = () => { +export default function HbStatisticsVerification() { const { setMessage: alert } = useAlertStore((state) => state); const queryClient = useQueryClient(); @@ -207,7 +207,9 @@ const HbStatisticsVerification = () => { params.verifyDtEnd = dayjs(to).format("YYYY-MM-DD"); } - return stAnlsMstApi.getAnalysisHistoryList(params); + return statisticsAnalysisMasterService.getAnalysisHistoryList( + params, + ); }, refetchInterval: (query) => { const data = query.state.data; @@ -248,9 +250,10 @@ const HbStatisticsVerification = () => { ], queryFn: async () => { if (!outlierDialog.anlsSn) return []; - const response = await stAnlsMstApi.getOutlierList( - outlierDialog.anlsSn, - ); + const response = + await statisticsAnalysisMasterService.getOutlierList( + outlierDialog.anlsSn, + ); return response; }, enabled: outlierDialog.open && !!outlierDialog.anlsSn, @@ -771,6 +774,4 @@ const HbStatisticsVerification = () => { ); -}; - -export default HbStatisticsVerification; +} diff --git a/apps/st/src/components/statistics/verification/ImrVerificationHistory.tsx b/apps/st/src/components/statistics/verification/ImrVerificationHistory.tsx index ad9990e95..209856e6a 100644 --- a/apps/st/src/components/statistics/verification/ImrVerificationHistory.tsx +++ b/apps/st/src/components/statistics/verification/ImrVerificationHistory.tsx @@ -24,25 +24,21 @@ import dayjs from "dayjs"; import { ENDPOINT } from "@/shared/config"; import { CodeSelect } from "@/entities/code"; -import type { - AdstZoneResponseDto, - StAnlsMstResponse, - StAnlsMstSearchParams, - StAnlsRsltResponse, -} from "@/entities/statistics"; import { + type AdstZoneResponseDto, IMR_DETAIL_VISIBLE_COLUMNS_BY_STATS_CD, IMR_DETAIL_VISIBLE_COLUMNS_DEFAULT, + imrApi, imrDetailGridColumnInfo, imrVerificationHistoryColumns, -} from "@/entities/statistics"; -import { imrApi } from "@/entities/statistics/api/imr-api"; -import { + type StAnlsMstResponse, + type StAnlsMstSearchParams, + stAnlsRsltApi, + type StAnlsRsltResponse, + statisticsAnalysisMasterService, useStAnlsSidoCombo, useStAnlsSigunguCombo, -} from "@/entities/statistics/api/queries"; -import { stAnlsMstApi } from "@/entities/statistics/api/statistics-analysis-master-api"; -import { stAnlsRsltApi } from "@/entities/statistics/api/statistics-analysis-result-api"; +} from "@/entities/statistics"; import { Button, DataTable, @@ -144,7 +140,7 @@ const ImrVerificationHistory = () => { historyPagination, ], queryFn: () => - stAnlsMstApi.getImrAnalysisMasterList({ + statisticsAnalysisMasterService.getImrAnalysisMasterList({ ...appliedSearchParams, page: historyPagination.pageIndex, size: historyPagination.pageSize, diff --git a/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx b/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx index 89a988fda..e9c17bff9 100644 --- a/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx +++ b/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx @@ -1,6 +1,18 @@ import React, { useState } from "react"; -import type { StAnlsDtlResponse } from "@/entities/statistics"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { useQueryClient } from "@tanstack/react-query"; +import type { AxiosError } from "axios"; + +import { ENDPOINT } from "@/shared/config"; +import { useAlertStore, useMutationWrapper } from "@/shared/lib"; +import type { ApiErrorResponse } from "@/shared/model"; +import type { + StAnlsDtlConfirmRequest, + StAnlsDtlConfirmResponse, + StAnlsDtlResponse, +} from "@/entities/statistics"; +import { statisticsAnalysisDetailService } from "@/entities/statistics"; import { Button } from "@common/components"; import Indicators from "./Indicators"; @@ -38,6 +50,47 @@ const HbVerificationDetail: React.FC = ({ vrfcTrgtNm: string; } | null>(null); + const queryClient = useQueryClient(); + const { setMessage: alert } = useAlertStore((state) => state); + const { confirm } = useConfirm(); + + const confirmMutation = useMutationWrapper< + StAnlsDtlConfirmResponse, + AxiosError, + StAnlsDtlConfirmRequest + >({ + mutationFn: (payload) => + statisticsAnalysisDetailService.confirmDetail(payload), + onSuccess: async () => { + setCheckedSns([]); + + await Promise.all([ + queryClient.invalidateQueries({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL, + "list", + anlsSn, + ], + }), + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST], + }), + ]); + }, + }); + + const handleConfirmClick = async () => { + if (checkedSns.length === 0) return; + + if (!(await confirm("검증확정 처리하시겠습니까?"))) return; + + const vrfcTrgtSnList = checkedSns + .map((value) => Number(value)) + .filter((value) => !Number.isNaN(value)); + + confirmMutation.mutate({ anlsSn, vrfcTrgtSnList }); + }; + const handleTargetSelect = (row: StAnlsDtlResponse) => { setSelectedVrfcTrgtSn(String(row.vrfcTrgtSn)); setSelectedTargetInfo({ @@ -70,7 +123,14 @@ const HbVerificationDetail: React.FC = ({ 검증 상세 - +
diff --git a/apps/st/src/components/statistics/verification/detail/VerificationList.tsx b/apps/st/src/components/statistics/verification/detail/VerificationList.tsx index 3966b3131..306c533a3 100644 --- a/apps/st/src/components/statistics/verification/detail/VerificationList.tsx +++ b/apps/st/src/components/statistics/verification/detail/VerificationList.tsx @@ -6,7 +6,7 @@ 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 { statisticsAnalysisDetailService } from "@/entities/statistics/api/statistics-analysis-detail-service"; import { DataTable } from "@common/components"; interface VerificationListProps { @@ -27,7 +27,11 @@ const VerificationList: React.FC = ({ const { data: detailData, isLoading } = useQuery({ queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL, "list", anlsSn], queryFn: () => - stAnlsDtlApi.getDetailList({ anlsSn, page: 0, size: 100 }), + statisticsAnalysisDetailService.getDetailList({ + anlsSn, + page: 0, + size: 100, + }), enabled: !!anlsSn, }); diff --git a/apps/st/src/entities/statistics/api/queries.ts b/apps/st/src/entities/statistics/api/queries.ts index 30a6437ca..ff392a235 100644 --- a/apps/st/src/entities/statistics/api/queries.ts +++ b/apps/st/src/entities/statistics/api/queries.ts @@ -1,18 +1,18 @@ import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import { ENDPOINT } from "@/shared/config"; -import { generalverificationApiService } from "@/entities/statistics/api/general-verification-service"; -import { StatisticsInputStatusApi } from "@/entities/statistics/api/input/statistics-input-status-service"; -import type { VerifyResultPageData } from "@/entities/statistics/model/general-verification-interface"; + +import type { VerifyResultPageData } from "../model/general-verification-interface"; +import type { SewerStUserSearchParams } from "../model/sewer-st-user-interface"; import type { StatisticsVerificationFilterState, StatisticsVerificationSubFilterState, -} from "@/entities/statistics/model/statistics-verification-report/statistics-verification-report-types"; - -import type { SewerStUserSearchParams } from "../model/sewer-st-user-interface"; +} from "../model/statistics-verification-report/statistics-verification-report-types"; +import { StatisticsInputStatusApi } from "./input/statistics-input-status-service"; +import { generalverificationApiService } from "./general-verification-service"; import { sewerStUserApi } from "./sewer-st-user-service"; -import { stAnlsMstApi } from "./statistics-analysis-master-api"; +import { statisticsAnalysisMasterService } from "./statistics-analysis-master-service"; import { stAnlsRsltApi } from "./statistics-analysis-result-api"; const DETAIL_PAGE_SIZE = 100; @@ -20,14 +20,14 @@ const DETAIL_PAGE_SIZE = 100; export const useStAnlsSidoCombo = () => { return useQuery({ queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, "sido"], - queryFn: () => stAnlsMstApi.getSidoCombo(), + queryFn: () => statisticsAnalysisMasterService.getSidoCombo(), }); }; export const useStAnlsSigunguCombo = (sidoCd: string) => { return useQuery({ queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, "sigungu", sidoCd], - queryFn: () => stAnlsMstApi.getSigunguCombo(sidoCd), + queryFn: () => statisticsAnalysisMasterService.getSigunguCombo(sidoCd), enabled: Boolean(sidoCd), }); }; diff --git a/apps/st/src/entities/statistics/api/statistics-analysis-detail-api.ts b/apps/st/src/entities/statistics/api/statistics-analysis-detail-api.ts deleted file mode 100644 index 957680ec2..000000000 --- a/apps/st/src/entities/statistics/api/statistics-analysis-detail-api.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { client } from "@/shared/api"; -import { ENDPOINT } from "@/shared/config"; - -import type { - StAnlsDtlListApiResponse, - StAnlsDtlResponse, - StAnlsDtlSearchParams, -} from "../model/statistics-analysis/statistics-analysis-detail-interface"; - -export class StAnlsDtlApiService { - public async getDetailList( - params: StAnlsDtlSearchParams, - ): Promise { - const response = await client.get( - `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}`, - { - params, - }, - ); - - return response.data; - } - - public async getDetail( - anlsSn: number, - vrfcTrgtSn: number, - ): Promise { - const response = await client.get( - `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/${anlsSn}/${vrfcTrgtSn}`, - ); - - return response.data; - } -} - -export const stAnlsDtlApi = new StAnlsDtlApiService(); diff --git a/apps/st/src/entities/statistics/api/statistics-analysis-detail-service.ts b/apps/st/src/entities/statistics/api/statistics-analysis-detail-service.ts new file mode 100644 index 000000000..5d47c6925 --- /dev/null +++ b/apps/st/src/entities/statistics/api/statistics-analysis-detail-service.ts @@ -0,0 +1,51 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +import type { + StAnlsDtlConfirmRequest, + StAnlsDtlConfirmResponse, + StAnlsDtlListApiResponse, + StAnlsDtlResponse, + StAnlsDtlSearchParams, +} from "../model/statistics-analysis/statistics-analysis-detail-interface"; + +async function getDetailList( + params: StAnlsDtlSearchParams, +): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}`, + { + params, + }, + ); + + return response.data; +} + +async function getDetail( + anlsSn: number, + vrfcTrgtSn: number, +): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/${anlsSn}/${vrfcTrgtSn}`, + ); + + return response.data; +} + +async function confirmDetail( + request: StAnlsDtlConfirmRequest, +): Promise { + const response = await client.post( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/confirm`, + request, + ); + + return response.data; +} + +export const statisticsAnalysisDetailService = { + getDetailList, + getDetail, + confirmDetail, +}; diff --git a/apps/st/src/entities/statistics/api/statistics-analysis-master-api.ts b/apps/st/src/entities/statistics/api/statistics-analysis-master-api.ts deleted file mode 100644 index dfe951e53..000000000 --- a/apps/st/src/entities/statistics/api/statistics-analysis-master-api.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { client } from "@/shared/api"; -import { ENDPOINT } from "@/shared/config"; - -import type { - AdstZoneResponseDto, - StAnlsMstImrListApiResponse, - StAnlsMstListApiResponse, - StAnlsMstSearchParams, - StAnlsOutlierListResponse, -} from "../model/statistics-analysis/statistics-analysis-master-interface"; - -export class StAnlsMstApiService { - public async getAnalysisHistoryList( - params: StAnlsMstSearchParams, - ): Promise { - const response = await client.get( - `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}`, - { - params, - }, - ); - - return response.data; - } - - public async getImrAnalysisMasterList( - params: StAnlsMstSearchParams, - ): Promise { - const response = await client.get( - `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/imr`, - { - params, - }, - ); - - return response.data; - } - - public async getOutlierList( - anlsSn: number, - ): Promise { - const response = await client.get( - `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/outlierList/${anlsSn}`, - ); - - return response.data; - } - - public async getSidoCombo(): Promise { - const response = await client.get( - `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sido`, - ); - - return response.data; - } - - public async getSigunguCombo( - sidoCd: string, - ): Promise { - const response = await client.get( - `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sigungu`, - { params: { sidoCd } }, - ); - - return response.data; - } -} - -export const stAnlsMstApi = new StAnlsMstApiService(); diff --git a/apps/st/src/entities/statistics/api/statistics-analysis-master-service.ts b/apps/st/src/entities/statistics/api/statistics-analysis-master-service.ts new file mode 100644 index 000000000..a9e9d7918 --- /dev/null +++ b/apps/st/src/entities/statistics/api/statistics-analysis-master-service.ts @@ -0,0 +1,71 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +import type { + AdstZoneResponseDto, + StAnlsMstImrListApiResponse, + StAnlsMstListApiResponse, + StAnlsMstSearchParams, + StAnlsOutlierListResponse, +} from "../model/statistics-analysis/statistics-analysis-master-interface"; + +async function getAnalysisHistoryList( + params: StAnlsMstSearchParams, +): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}`, + { + params, + }, + ); + + return response.data; +} + +async function getImrAnalysisMasterList( + params: StAnlsMstSearchParams, +): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/imr`, + { + params, + }, + ); + + return response.data; +} + +async function getOutlierList( + anlsSn: number, +): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/outlierList/${anlsSn}`, + ); + + return response.data; +} + +async function getSidoCombo(): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sido`, + ); + + return response.data; +} + +async function getSigunguCombo(sidoCd: string): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sigungu`, + { params: { sidoCd } }, + ); + + return response.data; +} + +export const statisticsAnalysisMasterService = { + getAnalysisHistoryList, + getImrAnalysisMasterList, + getOutlierList, + getSidoCombo, + getSigunguCombo, +}; diff --git a/apps/st/src/entities/statistics/index.ts b/apps/st/src/entities/statistics/index.ts index 10666214b..159b0eb76 100644 --- a/apps/st/src/entities/statistics/index.ts +++ b/apps/st/src/entities/statistics/index.ts @@ -1,3 +1,4 @@ +export { analysisApi } from "./api/analysis-api"; export { useDashboardEcptReqCount, useDashboardStatsSubmissionRate, @@ -8,6 +9,7 @@ export { export { dashboardApi } from "./api/dashboard/dashboard-service"; export { useScheduleInfo } from "./api/dashboard/schedule-info-queries"; export { scheduleInfoApi } from "./api/dashboard/schedule-info-service"; +export { imrApi } from "./api/imr-api"; export { noticeTalkSendingApi } from "./api/notice-talk/notice-talk-sending-service"; export { useNtftalkTemplateList, @@ -21,7 +23,11 @@ export { useStatsVrfcRsltData, } from "./api/queries"; export { sewerStUserApi } from "./api/sewer-st-user-service"; +export { statisticsAnalysisDetailService } from "./api/statistics-analysis-detail-service"; +export { statisticsAnalysisMasterService } from "./api/statistics-analysis-master-service"; +export { stAnlsRsltApi } from "./api/statistics-analysis-result-api"; export { statisticsApprovalApi } from "./api/statistics-approval-service"; +export { verificationTargetManagementApi } from "./api/verification-target-management-service"; export { type AnalysisHistoryListApiResponse, type AnalysisRequestPayload, @@ -83,6 +89,8 @@ export { type SewerStUserSearchParams, } from "./model/sewer-st-user-interface"; export { + type StAnlsDtlConfirmRequest, + type StAnlsDtlConfirmResponse, type StAnlsDtlListApiResponse, type StAnlsDtlResponse, type StAnlsDtlSearchParams, @@ -134,3 +142,4 @@ export { type IMRArticleItem, type ValidationArticleItem, } from "./model/verification-article/verification-article-interface"; +export { type VerificationTargetCodeResponse } from "./model/verification-target-management-interface"; diff --git a/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-detail-interface.ts b/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-detail-interface.ts index af0919b87..81d9c5840 100644 --- a/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-detail-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-detail-interface.ts @@ -18,6 +18,8 @@ export interface StAnlsDtlResponse { nmlDataNocs: number; // 정상 데이터 건수 exclDataNocs: number; // 제외 데이터 건수 otlrDataNocs: number; // 이상치 데이터 건수 + cfmtnYn: string; // 확정여부 (Y/N) + cfmtnDt: string | null; // 확정일자 (yyyy-MM-dd HH:mm:ss) statsCd: string; // 통계 코드 statsNm: string; // 통계 항목 명 vrfcTrgtNm: string; // 검증대상명 @@ -30,3 +32,13 @@ export interface StAnlsDtlSearchParams extends PageableParams { } export type StAnlsDtlListApiResponse = PageableList; + +export interface StAnlsDtlConfirmRequest { + anlsSn: number; // 분석 일련번호 + vrfcTrgtSnList: number[]; // 검증 대상 일련번호 목록 +} + +export interface StAnlsDtlConfirmResponse { + anlsSn: number; // 분석 일련번호 + processedCount: number; // 처리된 검증 대상 건수 +} diff --git a/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-master-interface.ts b/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-master-interface.ts index 1129c14a8..1e3e6836f 100644 --- a/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-master-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-master-interface.ts @@ -45,13 +45,15 @@ export interface StAnlsMstResponseDto { /** 검증대상명 */ vrfcTrgtNm: string; /** 분석진행비율 */ - anlsPrgrsRt: number; + anlsPrgrsRt: number | null; /** 하수도사업자코드 */ sewerBzmnCd: string; /** 통계코드 */ statsCd: string; /** 검증방법코드 */ vrfcMthdCd: string; + /** 확정건수/총건수 (예: 3/10) */ + cfmtnYnCntTotalCnt: string; } /** @@ -132,7 +134,6 @@ export interface StAnlsOutlierListResponseDto { outlierDataList: StAnlsOutlierDataResponseDto[]; } -/** 기존 인터페이스 호환을 위한 별칭 (필요 시 유지) */ export type StAnlsMstResponse = StAnlsMstResponseDto; export type StAnlsMstImrResponse = StAnlsMstImrResponseDto; export type StAnlsMstSearchParams = StAnlsMstSearchDto; diff --git a/apps/st/src/entities/statistics/model/statistics-analysis/verification-history-columns.tsx b/apps/st/src/entities/statistics/model/statistics-analysis/verification-history-columns.tsx index edd85652e..d83e415db 100644 --- a/apps/st/src/entities/statistics/model/statistics-analysis/verification-history-columns.tsx +++ b/apps/st/src/entities/statistics/model/statistics-analysis/verification-history-columns.tsx @@ -77,6 +77,12 @@ export const verificationHistoryColumns = [
{info.getValue() ?? "-"}
), }), + columnHelper.accessor("cfmtnYnCntTotalCnt", { + header: "확정건수/총", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), columnHelper.accessor("nmlDataNocs", { header: "정상건수", cell: (info) => ( diff --git a/apps/st/src/entities/statistics/model/statistics-analysis/verification-list-columns.tsx b/apps/st/src/entities/statistics/model/statistics-analysis/verification-list-columns.tsx index 05d0ddae5..bdea4cca9 100644 --- a/apps/st/src/entities/statistics/model/statistics-analysis/verification-list-columns.tsx +++ b/apps/st/src/entities/statistics/model/statistics-analysis/verification-list-columns.tsx @@ -1,6 +1,7 @@ import React from "react"; import type { ColumnDef } from "@tanstack/react-table"; +import dayjs from "dayjs"; import { Checkbox } from "@common/components"; @@ -16,94 +17,123 @@ export const getVerificationListColumns = ({ rowData, checkedSns, onCheck, -}: GetVerificationListColumnsParams): ColumnDef[] => [ - { - id: "checkbox", - header: () => { - const isAllChecked = - rowData.length > 0 && checkedSns.length === rowData.length; - return ( -
- { - if (value) { - onCheck( - rowData.map((r) => String(r.vrfcTrgtSn)), - rowData, +}: GetVerificationListColumnsParams): ColumnDef[] => { + const selectableRows = rowData.filter((r) => r.cfmtnYn !== "Y"); + + return [ + { + id: "checkbox", + header: () => { + const isAllChecked = + selectableRows.length > 0 && + checkedSns.length === selectableRows.length; + return ( +
+ { + if (value) { + onCheck( + selectableRows.map((r) => + String(r.vrfcTrgtSn), + ), + selectableRows, + ); + } else { + onCheck([], []); + } + }} + aria-label="Select all rows" + /> +
+ ); + }, + cell: ({ row }) => { + if (row.original.cfmtnYn === "Y") { + return
; + } + const rowId = String(row.original.vrfcTrgtSn); + const isChecked = checkedSns.includes(rowId); + return ( +
+ { + const newCheckedSns = value + ? [...checkedSns, rowId] + : checkedSns.filter((id) => id !== rowId); + const newCheckedRows = selectableRows.filter( + (r) => + newCheckedSns.includes( + String(r.vrfcTrgtSn), + ), ); - } else { - onCheck([], []); + onCheck(newCheckedSns, newCheckedRows); + }} + onClick={(e: React.MouseEvent) => + e.stopPropagation() } - }} - aria-label="Select all rows" - /> -
- ); + aria-label={`Select row ${rowId}`} + /> +
+ ); + }, + enableSorting: false, + enableHiding: false, }, - cell: ({ row }) => { - const rowId = String(row.original.vrfcTrgtSn); - const isChecked = checkedSns.includes(rowId); - return ( -
- { - 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}`} - /> -
- ); + { + header: "통계 년도", + accessorKey: "statsYr", }, - 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", - }, -]; + { + header: "통계 항목", + accessorKey: "statsNm", + }, + { + header: "검증 항목", + accessorKey: "vrfcTrgtNm", + }, + { + header: "확정일자", + accessorKey: "cfmtnDt", + cell: ({ row }) => { + return ( +
+ {row.original.cfmtnDt + ? dayjs(row.original.cfmtnDt).format("YYYY-MM-DD") + : "-"} +
+ ); + }, + }, + { + header: "μ", + accessorKey: "ipcrCntVl", + }, + { + header: "Percentile", + accessorKey: "pctlCrtrVl", + }, + { + header: "A", + accessorKey: "scopAjmtCntVl", + }, + { + header: "C", + accessorKey: "thrsCntVl", + }, + { + header: "정상건수", + accessorKey: "nmlDataNocs", + }, + { + header: "제외건수", + accessorKey: "exclDataNocs", + }, + { + header: "이상건수", + accessorKey: "otlrDataNocs", + }, + ]; +}; diff --git a/apps/st/src/shared/lib/use-mutation-wrapper.tsx b/apps/st/src/shared/lib/use-mutation-wrapper.tsx index cea1a8f83..7b378dc86 100644 --- a/apps/st/src/shared/lib/use-mutation-wrapper.tsx +++ b/apps/st/src/shared/lib/use-mutation-wrapper.tsx @@ -1,29 +1,30 @@ -import { AxiosError } from "axios"; import { useMutation, type UseMutationOptions } from "@tanstack/react-query"; +import { AxiosError } from "axios"; + import { useAlertStore } from "./use-alert-store"; export const useMutationWrapper = < - TData = unknown, - TError = unknown, - TVariables = void, - TContext = unknown, + TData = unknown, + TError = unknown, + TVariables = void, + TContext = unknown, >( - mutationOptions: UseMutationOptions, + mutationOptions: UseMutationOptions, ) => { - const alert = useAlertStore((state) => state.setMessage); - return useMutation({ - ...mutationOptions, - onError: (error) => { - let errorMessage = ""; + const alert = useAlertStore((state) => state.setMessage); + return useMutation({ + ...mutationOptions, + onError: (error) => { + let errorMessage = ""; - if (error instanceof AxiosError) { - if (error.response?.data?.message) - errorMessage += `${error.response.data.message}\n`; - if (error.response?.data?.errors[0]?.message) - errorMessage += `${error.response.data.errors[0].message}\n`; - } + if (error instanceof AxiosError) { + if (error.response?.data?.message) + errorMessage += `${error.response.data.message}\n`; + if (error.response?.data?.errors[0]?.message) + errorMessage += `${error.response.data.errors[0].message}\n`; + } - alert(`${errorMessage}`); - }, - }); + alert(`${errorMessage}`); + }, + }); }; From 02655510bd60c75c92829f8694bbc9697a60d83e Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 8 May 2026 17:05:35 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[st]=20refactor:=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=ED=86=A1=20=EB=B0=9C=EC=86=A1=20=ED=8E=98=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/NoticeTalkSending.tsx | 55 +++++++++---------- .../notice-talk-sending-service.ts | 12 ++++ 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/apps/st/src/components/statistics/management/NoticeTalkSending.tsx b/apps/st/src/components/statistics/management/NoticeTalkSending.tsx index ff0cae557..df0076a76 100644 --- a/apps/st/src/components/statistics/management/NoticeTalkSending.tsx +++ b/apps/st/src/components/statistics/management/NoticeTalkSending.tsx @@ -8,14 +8,15 @@ import { type RowSelectionState, useReactTable, } from "@tanstack/react-table"; +import type { AxiosError } from "axios"; -import { client } from "@/shared/api/client"; -import { ENDPOINT } from "@/shared/config"; -import { useAlertStore } from "@/shared/lib"; +import { useAlertStore, useMutationWrapper } from "@/shared/lib"; +import type { ApiErrorResponse } from "@/shared/model"; import { type AdstZoneResponseDto, type BusinessSendRequest, type BusinessSendResponse, + noticeTalkSendingApi, type SelectedRecipient, type SewerStUserItem, useNtftalkTemplateList, @@ -122,10 +123,26 @@ function NtitlkSndng() { const [previewRecipient, setPreviewRecipient] = useState(null); const [isPreviewOpen, setIsPreviewOpen] = useState(false); - const [isSending, setIsSending] = useState(false); const [userListRowSelection, setUserListRowSelection] = useState({}); + const sendMutation = useMutationWrapper< + BusinessSendResponse, + AxiosError, + BusinessSendRequest + >({ + mutationFn: (payload) => noticeTalkSendingApi.sendBusiness(payload), + onSuccess: (data) => { + if (!data.success) { + alert(data.message || "알림톡 발송 요청에 실패했습니다."); + return; + } + alert(data.message || "알림톡 발송이 요청되었습니다."); + setSelectedRecipients([]); + setCommonVariableValues({}); + }, + }); + const { data: templatePageData } = useNtftalkTemplateList(); const templates = templatePageData?.content ?? []; @@ -321,7 +338,7 @@ function NtitlkSndng() { }), ); if (missingVariable) { - alert("필수 변수값이 입력되지 않은 발송 대상이 있습니다."); + alert("변수값이 입력되지 않은 발송 대상이 있습니다."); return; } if ( @@ -331,34 +348,13 @@ function NtitlkSndng() { ) return; - const payload: BusinessSendRequest = { + sendMutation.mutate({ templateCode: selectedTemplateCode, callback: "032-590-4000", recipients: selectedRecipients.map((recipient) => getMergedVariables(recipient, commonVariableValues), ), - }; - - try { - setIsSending(true); - const response = await client.post( - `${ENDPOINT.NTFTALK_SERVICE.ATA_SEND}/business`, - payload, - ); - const data = response.data; - if (!data.success) { - alert(data.message || "알림톡 발송 요청에 실패했습니다."); - return; - } - - alert(data.message || `알림톡 발송이 요청되었습니다.`); - setSelectedRecipients([]); - setCommonVariableValues({}); - } catch { - alert("알림톡 발송 중 오류가 발생했습니다."); - } finally { - setIsSending(false); - } + }); }; return ( @@ -567,7 +563,8 @@ function NtitlkSndng() { color="green" onClick={handleSend} disabled={ - isSending || selectedRecipients.length === 0 + sendMutation.isPending || + selectedRecipients.length === 0 } > 발송 diff --git a/apps/st/src/entities/statistics/api/notice-talk/notice-talk-sending-service.ts b/apps/st/src/entities/statistics/api/notice-talk/notice-talk-sending-service.ts index 476c8fecd..a770b45e2 100644 --- a/apps/st/src/entities/statistics/api/notice-talk/notice-talk-sending-service.ts +++ b/apps/st/src/entities/statistics/api/notice-talk/notice-talk-sending-service.ts @@ -3,6 +3,8 @@ import { ENDPOINT } from "@/shared/config"; import type { PageableList } from "@/shared/model"; import type { + BusinessSendRequest, + BusinessSendResponse, NtftalkTemplate, TemplateVariablesResponse, } from "../../model/notice-talk/notice-talk-sending-interface"; @@ -38,6 +40,16 @@ export class NoticeTalkSendingApiService { ); return response.data; } + + async sendBusiness( + payload: BusinessSendRequest, + ): Promise { + const response = await client.post( + `${ENDPOINT.NTFTALK_SERVICE.ATA_SEND}/business`, + payload, + ); + return response.data; + } } export const noticeTalkSendingApi = new NoticeTalkSendingApiService(); From bd1e1e3d1ff2936020a61f4ad302993bb882de9e Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 8 May 2026 17:30:53 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[st]=20chore:=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=9D=B8=ED=92=8B=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../verification/HbStatisticsVerification.tsx | 40 +++++++++++++------ .../verification/VerificationTargetDialog.tsx | 5 +-- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx index 2206b5d3b..4dd37c89d 100644 --- a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx +++ b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx @@ -114,10 +114,10 @@ export default function HbStatisticsVerification() { const [verifyType, setVerifyType] = useState("ALL"); const [formState, setFormState] = useState({ - mu: "0", - percentile: "0", - a: "0", - c: "0", + mu: "", + percentile: "", + a: "", + c: "", verificationYear: defaultYear, }); const [selectedTargets, setSelectedTargets] = useState([]); @@ -559,11 +559,14 @@ export default function HbStatisticsVerification() {
-
-
-
-
diff --git a/apps/st/src/components/statistics/verification/VerificationTargetDialog.tsx b/apps/st/src/components/statistics/verification/VerificationTargetDialog.tsx index ce9b1d26e..eb56ef7d8 100644 --- a/apps/st/src/components/statistics/verification/VerificationTargetDialog.tsx +++ b/apps/st/src/components/statistics/verification/VerificationTargetDialog.tsx @@ -4,7 +4,6 @@ import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Search } from "lucide-react"; import { CodeSelect } from "@/entities/code/ui/code-select"; import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service"; @@ -368,7 +367,7 @@ export function VerificationTargetDialog({ type="number" min="0" max="0.5" - step="0.1" + step="0.01" value={ field.value ?? "" @@ -385,7 +384,7 @@ export function VerificationTargetDialog({ : null, ) } - placeholder="(0 ~ 0.5, 0.1 단위)" + placeholder="(0 ~ 0.5, 0.01 단위)" />
)}