Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev

This commit is contained in:
2026-05-08 17:53:30 +09:00
18 changed files with 464 additions and 303 deletions
@@ -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<SelectedRecipient | null>(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const [isSending, setIsSending] = useState(false);
const [userListRowSelection, setUserListRowSelection] =
useState<RowSelectionState>({});
const sendMutation = useMutationWrapper<
BusinessSendResponse,
AxiosError<ApiErrorResponse>,
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<BusinessSendResponse>(
`${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
}
>
발송
@@ -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();
@@ -114,10 +114,10 @@ const HbStatisticsVerification = () => {
const [verifyType, setVerifyType] = useState<VerifyType>("ALL");
const [formState, setFormState] = useState<VerificationFormState>({
mu: "0",
percentile: "0",
a: "0",
c: "0",
mu: "",
percentile: "",
a: "",
c: "",
verificationYear: defaultYear,
});
const [selectedTargets, setSelectedTargets] = useState<string[]>([]);
@@ -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,
@@ -556,11 +559,14 @@ const HbStatisticsVerification = () => {
<div className="space-y-3">
<div className="flex items-center gap-4">
<Label className="w-32 text-label">
μ
<Label className="w-48 whitespace-nowrap text-label">
중요도 계수(μ)
</Label>
<Input
type="number"
min="0"
max="1"
step="0.1"
value={formState.mu}
onChange={(event) =>
handleFormChange(
@@ -569,15 +575,19 @@ const HbStatisticsVerification = () => {
)
}
className="flex-1"
placeholder="(0 ~ 1, 0.1 단위)"
/>
</div>
<div className="flex items-center gap-4">
<Label className="w-32 text-label">
Percentile
<Label className="w-48 text-label">
백분위수 기준값(pct)
</Label>
<Input
type="number"
min="0"
max="0.5"
step="0.01"
value={formState.percentile}
onChange={(event) =>
handleFormChange(
@@ -586,15 +596,19 @@ const HbStatisticsVerification = () => {
)
}
className="flex-1"
placeholder="(0 ~ 0.5, 0.01 단위)"
/>
</div>
<div className="flex items-center gap-4">
<Label className="w-32 text-label">
A
<Label className="w-48 text-label">
스케일 조정 계수(A)
</Label>
<Input
type="number"
min="0"
max="1"
step="0.01"
value={formState.a}
onChange={(event) =>
handleFormChange(
@@ -603,15 +617,19 @@ const HbStatisticsVerification = () => {
)
}
className="flex-1"
placeholder="(0 ~ 1, 0.01 단위)"
/>
</div>
<div className="flex items-center gap-4">
<Label className="w-32 text-label">
C
<Label className="w-48 text-label">
임계 계수(c)
</Label>
<Input
type="number"
min="0"
max="100"
step="1"
value={formState.c}
onChange={(event) =>
handleFormChange(
@@ -620,6 +638,7 @@ const HbStatisticsVerification = () => {
)
}
className="flex-1"
placeholder="(0 ~ 100, 1 단위)"
/>
</div>
</div>
@@ -771,6 +790,4 @@ const HbStatisticsVerification = () => {
</Dialog>
</>
);
};
export default HbStatisticsVerification;
}
@@ -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,
@@ -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 단위)"
/>
</div>
)}
@@ -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<HbVerificationDetailProps> = ({
vrfcTrgtNm: string;
} | null>(null);
const queryClient = useQueryClient();
const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm();
const confirmMutation = useMutationWrapper<
StAnlsDtlConfirmResponse,
AxiosError<ApiErrorResponse>,
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<HbVerificationDetailProps> = ({
검증 상세
</h2>
</div>
<Button disabled={checkedSns.length === 0}>검증확정</Button>
<Button
disabled={
checkedSns.length === 0 || confirmMutation.isPending
}
onClick={handleConfirmClick}
>
검증확정
</Button>
</div>
<div className="flex flex-col gap-4">
@@ -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<VerificationListProps> = ({
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,
});
@@ -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<BusinessSendResponse> {
const response = await client.post<BusinessSendResponse>(
`${ENDPOINT.NTFTALK_SERVICE.ATA_SEND}/business`,
payload,
);
return response.data;
}
}
export const noticeTalkSendingApi = new NoticeTalkSendingApiService();
@@ -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),
});
};
@@ -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<StAnlsDtlListApiResponse> {
const response = await client.get<StAnlsDtlListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}`,
{
params,
},
);
return response.data;
}
public async getDetail(
anlsSn: number,
vrfcTrgtSn: number,
): Promise<StAnlsDtlResponse> {
const response = await client.get<StAnlsDtlResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/${anlsSn}/${vrfcTrgtSn}`,
);
return response.data;
}
}
export const stAnlsDtlApi = new StAnlsDtlApiService();
@@ -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<StAnlsDtlListApiResponse> {
const response = await client.get<StAnlsDtlListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}`,
{
params,
},
);
return response.data;
}
async function getDetail(
anlsSn: number,
vrfcTrgtSn: number,
): Promise<StAnlsDtlResponse> {
const response = await client.get<StAnlsDtlResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/${anlsSn}/${vrfcTrgtSn}`,
);
return response.data;
}
async function confirmDetail(
request: StAnlsDtlConfirmRequest,
): Promise<StAnlsDtlConfirmResponse> {
const response = await client.post<StAnlsDtlConfirmResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/confirm`,
request,
);
return response.data;
}
export const statisticsAnalysisDetailService = {
getDetailList,
getDetail,
confirmDetail,
};
@@ -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<StAnlsMstListApiResponse> {
const response = await client.get<StAnlsMstListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}`,
{
params,
},
);
return response.data;
}
public async getImrAnalysisMasterList(
params: StAnlsMstSearchParams,
): Promise<StAnlsMstImrListApiResponse> {
const response = await client.get<StAnlsMstImrListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/imr`,
{
params,
},
);
return response.data;
}
public async getOutlierList(
anlsSn: number,
): Promise<StAnlsOutlierListResponse[]> {
const response = await client.get<StAnlsOutlierListResponse[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/outlierList/${anlsSn}`,
);
return response.data;
}
public async getSidoCombo(): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sido`,
);
return response.data;
}
public async getSigunguCombo(
sidoCd: string,
): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sigungu`,
{ params: { sidoCd } },
);
return response.data;
}
}
export const stAnlsMstApi = new StAnlsMstApiService();
@@ -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<StAnlsMstListApiResponse> {
const response = await client.get<StAnlsMstListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}`,
{
params,
},
);
return response.data;
}
async function getImrAnalysisMasterList(
params: StAnlsMstSearchParams,
): Promise<StAnlsMstImrListApiResponse> {
const response = await client.get<StAnlsMstImrListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/imr`,
{
params,
},
);
return response.data;
}
async function getOutlierList(
anlsSn: number,
): Promise<StAnlsOutlierListResponse[]> {
const response = await client.get<StAnlsOutlierListResponse[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/outlierList/${anlsSn}`,
);
return response.data;
}
async function getSidoCombo(): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sido`,
);
return response.data;
}
async function getSigunguCombo(sidoCd: string): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sigungu`,
{ params: { sidoCd } },
);
return response.data;
}
export const statisticsAnalysisMasterService = {
getAnalysisHistoryList,
getImrAnalysisMasterList,
getOutlierList,
getSidoCombo,
getSigunguCombo,
};
+9
View File
@@ -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";
@@ -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<StAnlsDtlResponse>;
export interface StAnlsDtlConfirmRequest {
anlsSn: number; // 분석 일련번호
vrfcTrgtSnList: number[]; // 검증 대상 일련번호 목록
}
export interface StAnlsDtlConfirmResponse {
anlsSn: number; // 분석 일련번호
processedCount: number; // 처리된 검증 대상 건수
}
@@ -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;
@@ -77,6 +77,12 @@ export const verificationHistoryColumns = [
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
),
}),
columnHelper.accessor("cfmtnYnCntTotalCnt", {
header: "확정건수/총",
cell: (info) => (
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
),
}),
columnHelper.accessor("nmlDataNocs", {
header: "정상건수",
cell: (info) => (
@@ -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<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,
}: GetVerificationListColumnsParams): ColumnDef<StAnlsDtlResponse>[] => {
const selectableRows = rowData.filter((r) => r.cfmtnYn !== "Y");
return [
{
id: "checkbox",
header: () => {
const isAllChecked =
selectableRows.length > 0 &&
checkedSns.length === selectableRows.length;
return (
<div className="flex items-center justify-center">
<Checkbox
checked={isAllChecked}
disabled={selectableRows.length === 0}
onCheckedChange={(value: boolean) => {
if (value) {
onCheck(
selectableRows.map((r) =>
String(r.vrfcTrgtSn),
),
selectableRows,
);
} else {
onCheck([], []);
}
}}
aria-label="Select all rows"
/>
</div>
);
},
cell: ({ row }) => {
if (row.original.cfmtnYn === "Y") {
return <div className="flex items-center justify-center" />;
}
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 = selectableRows.filter(
(r) =>
newCheckedSns.includes(
String(r.vrfcTrgtSn),
),
);
} else {
onCheck([], []);
onCheck(newCheckedSns, newCheckedRows);
}}
onClick={(e: React.MouseEvent) =>
e.stopPropagation()
}
}}
aria-label="Select all rows"
/>
</div>
);
aria-label={`Select row ${rowId}`}
/>
</div>
);
},
enableSorting: false,
enableHiding: false,
},
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>
);
{
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 (
<div className="text-center">
{row.original.cfmtnDt
? dayjs(row.original.cfmtnDt).format("YYYY-MM-DD")
: "-"}
</div>
);
},
},
{
header: "μ",
accessorKey: "ipcrCntVl",
},
{
header: "Percentile",
accessorKey: "pctlCrtrVl",
},
{
header: "A",
accessorKey: "scopAjmtCntVl",
},
{
header: "C",
accessorKey: "thrsCntVl",
},
{
header: "정상건수",
accessorKey: "nmlDataNocs",
},
{
header: "제외건수",
accessorKey: "exclDataNocs",
},
{
header: "이상건수",
accessorKey: "otlrDataNocs",
},
];
};
+21 -20
View File
@@ -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<TData, TError, TVariables, TContext>,
mutationOptions: UseMutationOptions<TData, TError, TVariables, TContext>,
) => {
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}`);
},
});
};