[st] feat: 입력 - 제출 시 imr 요청 및 상태 확인, 진행 progress bar 추가
This commit is contained in:
@@ -12,6 +12,7 @@ import { AlertDialog } from "@/shared/ui/alert-dialog";
|
||||
import { PromptDialog } from "@/shared/ui/prompt-dialog";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { SelectPromptDialog } from "@/shared/ui/select-prompt-dialog";
|
||||
import { GlobalProgressOverlay } from "@/shared/ui/global-progress-overlay";
|
||||
import { PromptProvider, StoreProvider } from "@/shared/providers";
|
||||
import { AlertDialog as RsoneAlertDialog } from "@rsone/components";
|
||||
|
||||
@@ -36,6 +37,7 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
||||
<PromptDialog />
|
||||
<ConfirmDialog />
|
||||
<SelectPromptDialog />
|
||||
<GlobalProgressOverlay />
|
||||
<Toaster position="top-center" />
|
||||
</AuthWrapper>
|
||||
</ConfirmProvider>
|
||||
|
||||
@@ -6,9 +6,9 @@ interface VerificationResultButtonProps {
|
||||
|
||||
const VerificationResultButton = ({ VerificationResultButtonFunc }: VerificationResultButtonProps) => {
|
||||
return (
|
||||
<Button color="white" className="h-[3.2rem] w-[96px]" onClick={VerificationResultButtonFunc}>
|
||||
<Button color="white" className="h-[3.2rem]" onClick={VerificationResultButtonFunc}>
|
||||
<i className="diveicon di-list" />
|
||||
<span>검증 결과</span>
|
||||
<span>지난 검증 결과</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
@apply !bg-[#5987bf] !text-[#fff];
|
||||
}
|
||||
|
||||
.rg-grid .rg-header .rg-table tr td,
|
||||
.rg-grid .rg-head .rg-table tr td {
|
||||
border: none;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell {
|
||||
@apply !bg-white;
|
||||
}
|
||||
@@ -138,4 +132,10 @@
|
||||
.rg-grid .rg-body .rg-table tr.rg-alternate-row td,
|
||||
.rg-grid .rg-body .rg-table tr.rg-alternate-row .rg-data-cell {
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
|
||||
.rg-grid .rg-header .rg-table tr td,
|
||||
.rg-grid .rg-head .rg-table tr td {
|
||||
border: none;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ApiResponse,
|
||||
SearchCondition,
|
||||
@@ -33,14 +33,14 @@ import { doList } from "../util/column/input/DoList";
|
||||
const getSidoListFetcher = async (
|
||||
key: [string, string],
|
||||
): Promise<ApiResponse<SearchCondition>> => {
|
||||
return statisticsAPi.getSidoList({ instSeCd: key[1] });
|
||||
return statisticsApi.getSidoList({ instSeCd: key[1] });
|
||||
};
|
||||
|
||||
const getSggListFetcher = async (
|
||||
key: [string, string, string, string],
|
||||
): Promise<ApiResponse<Sgg[]>> => {
|
||||
const gugunSkipYn = doList.includes(key[3]) ? "N" : "Y";
|
||||
return statisticsAPi.getSggAllList(key[1], {
|
||||
return statisticsApi.getSggAllList(key[1], {
|
||||
instSeCd: key[2],
|
||||
gugunSkipYn,
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ import CompactDataGrid from "./CompactDataGrid";
|
||||
import SaveButton from "../button/SaveButton";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ApiResponse,
|
||||
SearchCondition,
|
||||
@@ -91,13 +91,13 @@ const adminRegionColumnInfo: ColumnInfo = {
|
||||
};
|
||||
|
||||
const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
|
||||
return statisticsAPi.getSidoList();
|
||||
return statisticsApi.getSidoList();
|
||||
};
|
||||
|
||||
const getSggAllListFetcher = async (
|
||||
key: [string, string],
|
||||
): Promise<ApiResponse<Sgg[]>> => {
|
||||
return statisticsAPi.getSggAllList(key[1]);
|
||||
return statisticsApi.getSggAllList(key[1]);
|
||||
};
|
||||
|
||||
type SidoSggFacilityNameSearchFormProps = {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@common/components";
|
||||
|
||||
export interface VerificationResultDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function VerificationResultDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: VerificationResultDialogProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[640px]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>검증 결과</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="min-h-[120px]" />
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button type="button" color="gray">
|
||||
닫기
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
|
||||
|
||||
type VerificationResultGridRow = Record<string, string | number>;
|
||||
@@ -37,8 +37,9 @@ const verificationResultColumnInfo: ColumnInfo = {
|
||||
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "pbcpAprvYn", fieldName: "pbcpAprvYn", header: { text: "공단승인여부" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
|
||||
],
|
||||
columnLayout: ["ctpvNm", "sggNm", "vrfcTrgtNm", "rsnExpln", "nowVlCn", "compVlCn", "prcsYn", "pbcpAprvYn", "regDt"],
|
||||
columnLayout: ["ctpvNm", "sggNm", "vrfcTrgtNm", "rsnExpln", "nowVlCn", "compVlCn", "prcsYn", "pbcpAprvYn", "regDt", "mdfcnDt"],
|
||||
};
|
||||
|
||||
export interface VerificationResultHistoryDialogProps {
|
||||
@@ -93,12 +94,13 @@ export function VerificationResultHistoryDialog({
|
||||
sggNm: queryContext.sggNm,
|
||||
statsYr: queryContext.statsYr,
|
||||
statsCd: queryContext.statsCd,
|
||||
pbcpAprvYn: 'N',
|
||||
vrfcMthdCd,
|
||||
prcsYnList: "0,2,3",
|
||||
page: pageIndex + 1,
|
||||
size: 30,
|
||||
};
|
||||
return statisticsAPi.getVerificationResultList(param);
|
||||
return statisticsApi.getVerificationResultList(param);
|
||||
},
|
||||
[queryContext],
|
||||
);
|
||||
@@ -191,6 +193,7 @@ export function VerificationResultHistoryDialog({
|
||||
prcsYn: PRCS_YN_LABEL[item.prcsYn] ?? item.prcsYn,
|
||||
pbcpAprvYn: item.pbcpAprvYn,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
}));
|
||||
|
||||
const normalRows = useMemo(() => mapRows(normalEntries), [normalEntries]);
|
||||
@@ -243,7 +246,7 @@ export function VerificationResultHistoryDialog({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[80%]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogTitle>{`${title} - ${queryContext?.statsYr} ${queryContext?.ctpvNm} ${queryContext?.sggNm}`}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="max-h-[85vh] space-y-5 overflow-auto">
|
||||
{errorMessage && (
|
||||
|
||||
@@ -9,14 +9,15 @@ import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useAlertStore, useGlobalProgressStore } from "@/shared/lib";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
@@ -33,6 +34,7 @@ import type {
|
||||
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type {
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SubmitExceptionRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
@@ -49,6 +51,32 @@ const hasVerificationErrors = (verification?: SewerageSupplyRateVerificationResu
|
||||
return Object.values(verification).some((errors) => Array.isArray(errors) && errors.length > 0);
|
||||
};
|
||||
|
||||
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
/** IMR 상태가 COMPLETED 이고 job_prog 가 100일 때까지 폴링 */
|
||||
async function waitForImrJobCompleted(
|
||||
jobId: string,
|
||||
getStatus: (id: string) => Promise<ImrStatusResponse>,
|
||||
options?: {
|
||||
pollIntervalMs?: number;
|
||||
maxAttempts?: number;
|
||||
onProgress?: (jobProgress: number) => void;
|
||||
},
|
||||
): Promise<void> {
|
||||
const pollIntervalMs = options?.pollIntervalMs ?? 2000;
|
||||
const maxAttempts = options?.maxAttempts ?? 150;
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const res = await getStatus(jobId);
|
||||
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0)));
|
||||
if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return;
|
||||
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") {
|
||||
throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
function getSewerageSupplyRateParams(
|
||||
values: SearchFormValues,
|
||||
overrides?: { yr?: string; demoYr?: string; mode?: string }
|
||||
@@ -74,11 +102,16 @@ const SeweragePenetrationRateInp = () => {
|
||||
const [statusInfo, setStatusInfo] = useState<SewerageSupplyRateStatusInfo | null>(null);
|
||||
const [showDailog, setShowDailog] = useState<boolean>(false);
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
|
||||
/** 예외제출 버튼으로 연 다이얼로그인지 (확인 시 executeSubmit 대신 SubmitExceptionFunc 경로) */
|
||||
const [isExceptionSubmitFlow, setIsExceptionSubmitFlow] = useState(false);
|
||||
const [statsCd, setStatsCd] = useState<string>('');
|
||||
const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null);
|
||||
const { confirm } = useConfirm();
|
||||
const showGlobalProgress = useGlobalProgressStore((state) => state.show);
|
||||
const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress);
|
||||
const hideGlobalProgress = useGlobalProgressStore((state) => state.hide);
|
||||
const globalProgressVisible = useGlobalProgressStore((state) => state.visible);
|
||||
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
|
||||
@@ -218,7 +251,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await statisticsAPi.submitException(params);
|
||||
const response = await statisticsApi.submitException(params);
|
||||
|
||||
if (response.status === 'success') {
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false);
|
||||
@@ -277,7 +310,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
|
||||
const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
showGlobalProgress({ label: "제출 진행 중...", progress: 0 });
|
||||
const userInfo = await getUserInfo();
|
||||
const currentData = getGridDataRef.current?.();
|
||||
|
||||
@@ -354,6 +387,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
|
||||
const response = await statisticsInputSewSupplyRateApi.submitSewerageSupplyRate(requestData);
|
||||
if (response.status === 'success') {
|
||||
setGlobalProgress(50);
|
||||
|
||||
if (response.data.finalStatus === '5') {
|
||||
const imrParam = {
|
||||
@@ -366,16 +400,32 @@ const SeweragePenetrationRateInp = () => {
|
||||
};
|
||||
|
||||
try {
|
||||
await statisticsAPi.callImrService(imrParam);
|
||||
const imrResponse = await statisticsApi.callImrService(imrParam);
|
||||
if (imrResponse.job_id) {
|
||||
await waitForImrJobCompleted(imrResponse.job_id, (id) =>
|
||||
statisticsApi.getImrStatus(id),
|
||||
{
|
||||
onProgress: (jobProgress) => {
|
||||
setGlobalProgress(50 + Math.round(jobProgress / 2));
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
setGlobalProgress(100);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
alert('제출되었습니다.');
|
||||
} catch {
|
||||
setVerificationResultDialogOpen(true);
|
||||
} catch (e) {
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
setIsLoading(false);
|
||||
alert('IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.');
|
||||
const msg =
|
||||
e instanceof Error && e.message
|
||||
? e.message
|
||||
: 'IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.';
|
||||
alert(msg);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setGlobalProgress(100);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false);
|
||||
alert('제출되었습니다.');
|
||||
}
|
||||
@@ -385,14 +435,14 @@ const SeweragePenetrationRateInp = () => {
|
||||
} catch {
|
||||
alert('데이터 제출 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
hideGlobalProgress();
|
||||
}
|
||||
};
|
||||
|
||||
const saveFunc = async () => {
|
||||
try {
|
||||
if (await confirm('저장 하시겠습니까?')) {
|
||||
setIsLoading(true);
|
||||
showGlobalProgress({ label: "저장 진행 중...", progress: 0 });
|
||||
|
||||
const userInfo = await getUserInfo();
|
||||
const currentData = getGridDataRef.current?.();
|
||||
@@ -477,31 +527,10 @@ const SeweragePenetrationRateInp = () => {
|
||||
const response = await statisticsInputSewSupplyRateApi.saveSewerageSupplyRate(requestData);
|
||||
|
||||
if (response.status === 'success') {
|
||||
|
||||
if (response.data === '5') {
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: searchValues?.bsnmCd ?? '',
|
||||
stats_cd: statsCd,
|
||||
stats_yr: searchValues?.year ?? '',
|
||||
lae_sbmsn_stts_cd: (statusInfo?.laeSbmsnSttsCd === '4' ? '3' : '1'),
|
||||
gen_verify_stts_cd: '1',
|
||||
};
|
||||
|
||||
try {
|
||||
await statisticsAPi.callImrService(imrParam);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
alert('저장되었습니다.');
|
||||
} catch {
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
setIsLoading(false);
|
||||
alert('IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
alert('저장되었습니다.');
|
||||
}
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgress(100);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
alert('저장되었습니다.');
|
||||
} else {
|
||||
alert(response.message || '데이터 저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
@@ -511,7 +540,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
} catch {
|
||||
alert('데이터 저장 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
hideGlobalProgress();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -530,7 +559,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
statsCd: statsCd,
|
||||
};
|
||||
|
||||
const response = await statisticsAPi.cancelSubmit(requestData);
|
||||
const response = await statisticsApi.cancelSubmit(requestData);
|
||||
|
||||
if (response.status === 'success') {
|
||||
fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
@@ -585,7 +614,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
{isLoading && !globalProgressVisible && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
@@ -654,6 +683,10 @@ const SeweragePenetrationRateInp = () => {
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<VerificationResultDialog
|
||||
open={verificationResultDialogOpen}
|
||||
onOpenChange={setVerificationResultDialogOpen}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import useSWR from "swr";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { CodeSelectWithAll } from "@/entities/code";
|
||||
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ApiResponse,
|
||||
SearchCondition,
|
||||
@@ -36,13 +36,13 @@ import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
|
||||
return statisticsAPi.getSidoList();
|
||||
return statisticsApi.getSidoList();
|
||||
};
|
||||
|
||||
const getSggListFetcher = async (
|
||||
key: [string, string],
|
||||
): Promise<ApiResponse<Sgg[]>> => {
|
||||
return statisticsAPi.getSggList(key[1]);
|
||||
return statisticsApi.getSggList(key[1]);
|
||||
};
|
||||
|
||||
const LogHistory = () => {
|
||||
@@ -158,7 +158,7 @@ const LogHistory = () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await statisticsAPi.getLogList(param);
|
||||
const response = await statisticsApi.getLogList(param);
|
||||
|
||||
if (response.status === "success") {
|
||||
const lostList = response.data?.statsLogList ?? [];
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DialogTitle,
|
||||
} from "@common/components";
|
||||
|
||||
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
|
||||
export interface LogHistoryDetailDialogProps {
|
||||
open: boolean;
|
||||
@@ -30,7 +30,7 @@ export function LogHistoryDetailDialog({
|
||||
|
||||
const sn = row.statsLogSn;
|
||||
void (async () => {
|
||||
const response = await statisticsAPi.getLogDetail(
|
||||
const response = await statisticsApi.getLogDetail(
|
||||
sn !== undefined && sn !== null ? String(sn) : "",
|
||||
);
|
||||
console.log(response.data);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn";
|
||||
import type { SewerageSupplyRateReviewItem, statsStatusInfo } from "@/entities/statistics/model/statistics-sewerage-penetration-review-interface";
|
||||
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
@@ -141,7 +141,7 @@ const SeweragePenetrationRateRvw = () => {
|
||||
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const response = await statisticsAPi.submitReview({
|
||||
const response = await statisticsApi.submitReview({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
@@ -169,7 +169,7 @@ const SeweragePenetrationRateRvw = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsAPi.getReviewOpinionDetail({
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
@@ -191,7 +191,7 @@ const SeweragePenetrationRateRvw = () => {
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsAPi.saveReviewOpinion(param);
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
@@ -220,7 +220,7 @@ const SeweragePenetrationRateRvw = () => {
|
||||
if (await confirm('반려 처리 하시겠습니까?')) {
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const response = await statisticsAPi.saveReviewReturn({
|
||||
const response = await statisticsApi.saveReviewReturn({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
|
||||
@@ -24,9 +24,11 @@ import {
|
||||
type StatisticsService,
|
||||
type SubmitExceptionRequest,
|
||||
type SubmitExceptionResponse,
|
||||
type ImrResponse,
|
||||
type ImrStatusResponse,
|
||||
} from "../model/statistics-interface";
|
||||
|
||||
export class StatisticsAPiService implements StatisticsService {
|
||||
export class StatisticsApiService implements StatisticsService {
|
||||
public async getSidoList(params?: {
|
||||
instSeCd?: string;
|
||||
}): Promise<ApiResponse<SearchCondition>> {
|
||||
@@ -70,14 +72,21 @@ export class StatisticsAPiService implements StatisticsService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async callImrService(param: ImrRequest): Promise<null> {
|
||||
const response = await client.post<null>(
|
||||
public async callImrService(param: ImrRequest): Promise<ImrResponse> {
|
||||
const response = await client.post<ImrResponse>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.IMR}`,
|
||||
param,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getImrStatus(param: string): Promise<ImrStatusResponse> {
|
||||
const response = await client.get<ImrStatusResponse>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.IMR}/status/${param}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// 예외 제출
|
||||
public async submitException(
|
||||
param: SubmitExceptionRequest,
|
||||
@@ -157,4 +166,4 @@ export class StatisticsAPiService implements StatisticsService {
|
||||
}
|
||||
}
|
||||
|
||||
export const statisticsAPi = new StatisticsAPiService();
|
||||
export const statisticsApi = new StatisticsApiService();
|
||||
|
||||
@@ -44,6 +44,30 @@ export interface ImrRequest {
|
||||
gen_verify_stts_cd?: string;
|
||||
}
|
||||
|
||||
export interface ImrResponse {
|
||||
anls_sn: number;
|
||||
job_id: string;
|
||||
total_otlr_cnt: number;
|
||||
status_code: string;
|
||||
error_msg: string;
|
||||
processing_time_ms: number;
|
||||
job_prog: number;
|
||||
queue_position: number;
|
||||
}
|
||||
|
||||
export interface ImrStatusResponse {
|
||||
job_id: string;
|
||||
anls_sn: number;
|
||||
status: string;
|
||||
job_prog: number;
|
||||
total_otlr_cnt: number;
|
||||
total_nml_cnt: number;
|
||||
total_excl_cnt: number;
|
||||
processing_time_ms: number;
|
||||
error_msg: string;
|
||||
queue_position: string;
|
||||
}
|
||||
|
||||
export interface VerificationResultListRequest {
|
||||
statsYr: string;
|
||||
sewerBzmnCd?: string;
|
||||
@@ -249,7 +273,7 @@ export interface StatisticsService {
|
||||
getSggList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>>;
|
||||
getSggAllList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>>;
|
||||
cancelSubmit(param: cancelSubmitRequest): Promise<cancelSubmitResponse>;
|
||||
callImrService(param: ImrRequest): Promise<null>;
|
||||
callImrService(param: ImrRequest): Promise<ImrResponse>;
|
||||
submitException(param: SubmitExceptionRequest): Promise<SubmitExceptionResponse>;
|
||||
getLogList(param: StatisticsLogRequest): Promise<StatisticsLogResponse>;
|
||||
getLogDetail(param: string): Promise<StatisticsLogDetailResponse>;
|
||||
@@ -258,4 +282,5 @@ export interface StatisticsService {
|
||||
saveReviewReturn(param: ReviewReturnSaveRequest): Promise<ReviewReturnSaveResponse>;
|
||||
submitReview(param :ReviewSubmitRequest): Promise<ReviewSubmitResponse>;
|
||||
getVerificationResultList(param: VerificationResultListRequest): Promise<VerificationResultListResponse>;
|
||||
getImrStatus(param: string): Promise<ImrStatusResponse>;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ export { getCookie, setCookie, removeCookie } from "./cookie";
|
||||
export { useMutationWrapper } from "./use-mutation-wrapper";
|
||||
export { useAlertStore } from "./use-alert-store";
|
||||
export { useDataTable } from "./use-data-table";
|
||||
export { useGlobalProgressStore } from "./use-global-progress-store";
|
||||
export { useMenuStore } from "./use-menu-store";
|
||||
export { useUserStore } from "./use-user-store";
|
||||
export { usePrompt } from "./use-prompt";
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useContext } from "react";
|
||||
import { useStore } from "zustand";
|
||||
import type { GlobalProgressStore } from "../store/global-progress-store";
|
||||
import { StoreContext } from "../providers/store-provider";
|
||||
|
||||
export const useGlobalProgressStore = <T>(
|
||||
selector: (store: GlobalProgressStore) => T,
|
||||
): T => {
|
||||
const storeContext = useContext(StoreContext);
|
||||
|
||||
if (!storeContext) {
|
||||
throw new Error("useGlobalProgressStore must be used within StoreProvider");
|
||||
}
|
||||
|
||||
return useStore(storeContext.globalProgressStore, selector);
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { createContext, useRef } from "react";
|
||||
import {
|
||||
createAlertStore,
|
||||
createAttachmentStore,
|
||||
createGlobalProgressStore,
|
||||
createMenuStore,
|
||||
createUserStore,
|
||||
} from "../store";
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
type StoreContextType = {
|
||||
alertStore: ReturnType<typeof createAlertStore>;
|
||||
attachmentStore: ReturnType<typeof createAttachmentStore>;
|
||||
globalProgressStore: ReturnType<typeof createGlobalProgressStore>;
|
||||
menuStore: ReturnType<typeof createMenuStore>;
|
||||
userStore: ReturnType<typeof createUserStore>;
|
||||
};
|
||||
@@ -25,6 +27,7 @@ export const StoreProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
storeRef.current = {
|
||||
alertStore: createAlertStore(),
|
||||
attachmentStore: createAttachmentStore(),
|
||||
globalProgressStore: createGlobalProgressStore(),
|
||||
menuStore: createMenuStore(),
|
||||
userStore: createUserStore(),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createStore } from "zustand/vanilla";
|
||||
|
||||
export type GlobalProgressState = {
|
||||
visible: boolean;
|
||||
label: string;
|
||||
progress: number;
|
||||
};
|
||||
|
||||
export type GlobalProgressActions = {
|
||||
show: (payload?: { label?: string; progress?: number }) => void;
|
||||
setLabel: (label: string) => void;
|
||||
setProgress: (progress: number) => void;
|
||||
hide: () => void;
|
||||
};
|
||||
|
||||
export type GlobalProgressStore = GlobalProgressState & GlobalProgressActions;
|
||||
|
||||
export const defaultGlobalProgressState: GlobalProgressState = {
|
||||
visible: false,
|
||||
label: "진행 중...",
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
export const createGlobalProgressStore = (
|
||||
initState: GlobalProgressState = defaultGlobalProgressState,
|
||||
) => {
|
||||
return createStore<GlobalProgressStore>()((set) => ({
|
||||
...initState,
|
||||
show: (payload) =>
|
||||
set(() => ({
|
||||
visible: true,
|
||||
label: payload?.label ?? defaultGlobalProgressState.label,
|
||||
progress: Math.max(0, Math.min(100, payload?.progress ?? 0)),
|
||||
})),
|
||||
setLabel: (label) => set(() => ({ label })),
|
||||
setProgress: (progress) =>
|
||||
set(() => ({ progress: Math.max(0, Math.min(100, progress)) })),
|
||||
hide: () =>
|
||||
set(() => ({
|
||||
visible: false,
|
||||
progress: 0,
|
||||
label: defaultGlobalProgressState.label,
|
||||
})),
|
||||
}));
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
export { createAlertStore } from "./alert-store";
|
||||
export { createAttachmentStore } from "./attachment-store";
|
||||
export { createGlobalProgressStore } from "./global-progress-store";
|
||||
export { createMenuStore, type BbsType } from "./menu-store";
|
||||
export { createUserStore, type IUserInfo } from "./user-store";
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { Progress } from "@common/components";
|
||||
import { useGlobalProgressStore } from "@/shared/lib";
|
||||
|
||||
export const GlobalProgressOverlay = () => {
|
||||
const visible = useGlobalProgressStore((state) => state.visible);
|
||||
const label = useGlobalProgressStore((state) => state.label);
|
||||
const progress = useGlobalProgressStore((state) => state.progress);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[999] bg-black/35 [data-theme='black']:bg-black/55 backdrop-blur-[1px] rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="w-[34rem] rounded-md border border-border bg-background p-6 shadow-md">
|
||||
<p className="mb-3 text-center text-[1.3rem] text-foreground">
|
||||
{label} {progress}%
|
||||
</p>
|
||||
<Progress value={progress} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user