Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
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>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Button } from "@common/components";
|
||||
|
||||
interface VerificationResultButtonProps {
|
||||
VerificationResultButtonFunc: () => void;
|
||||
}
|
||||
|
||||
const VerificationResultButton = ({ VerificationResultButtonFunc }: VerificationResultButtonProps) => {
|
||||
return (
|
||||
<Button color="white" className="h-[3.2rem]" onClick={VerificationResultButtonFunc}>
|
||||
<i className="diveicon di-list" />
|
||||
<span>지난 검증 결과</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerificationResultButton;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -319,8 +319,10 @@ const DataGrid = ({
|
||||
rowData,
|
||||
setRowData,
|
||||
getGridDataRef,
|
||||
getGridRef,
|
||||
cellStyleCallback,
|
||||
cellTooltipCallback,
|
||||
onCellEdited,
|
||||
onRowClick,
|
||||
onItemChecked,
|
||||
onItemAllChecked,
|
||||
@@ -345,6 +347,7 @@ const DataGrid = ({
|
||||
const cellTooltipCallbackRef = useRef<CellTooltipCallback | undefined>(
|
||||
cellTooltipCallback,
|
||||
);
|
||||
const onCellEditedRef = useRef<typeof onCellEdited>(onCellEdited);
|
||||
const onScrollToBottomRef = useRef<(() => void) | undefined>(
|
||||
onScrollToBottom,
|
||||
);
|
||||
@@ -357,6 +360,7 @@ const DataGrid = ({
|
||||
onScrollToBottomRef.current = onScrollToBottom;
|
||||
onItemCheckedRef.current = onItemChecked;
|
||||
onItemAllCheckedRef.current = onItemAllChecked;
|
||||
onCellEditedRef.current = onCellEdited;
|
||||
|
||||
// highlightedRowId가 변경될 때 해당 행으로 스크롤 및 선택
|
||||
useEffect(() => {
|
||||
@@ -1149,6 +1153,33 @@ const DataGrid = ({
|
||||
};
|
||||
}, [onItemChecked, onItemAllChecked]);
|
||||
|
||||
useEffect(() => {
|
||||
const gridView = gridRef.current?.gridView;
|
||||
if (!gridView) return;
|
||||
|
||||
gridView.onCellEdited = (grid, itemIndex, _row, field) => {
|
||||
const fieldName = columns[field]?.fieldName;
|
||||
if (!fieldName) return;
|
||||
|
||||
const newValue = grid.getValue(itemIndex, fieldName);
|
||||
const prevRow = rowDataRef.current[itemIndex];
|
||||
const oldValue = prevRow?.[fieldName] ?? "";
|
||||
|
||||
onCellEditedRef.current?.({
|
||||
itemIndex,
|
||||
fieldName,
|
||||
oldValue,
|
||||
newValue,
|
||||
});
|
||||
};
|
||||
|
||||
return () => {
|
||||
if (gridView.onCellEdited) {
|
||||
gridView.onCellEdited = undefined;
|
||||
}
|
||||
};
|
||||
}, [columns, setRowData]);
|
||||
|
||||
return (
|
||||
<div className="mx-[0.6rem] flex flex-col gap-[0.6rem]">
|
||||
<input
|
||||
@@ -1223,7 +1254,6 @@ const DataGrid = ({
|
||||
ref={gridRef}
|
||||
autoGenerateField={true}
|
||||
layout={columnLayout}
|
||||
rows={rowData}
|
||||
onInitialized={(grid) => {
|
||||
if (!grid) return;
|
||||
(
|
||||
@@ -1259,9 +1289,9 @@ const DataGrid = ({
|
||||
buildFieldsFromColumns(columns),
|
||||
);
|
||||
}
|
||||
if (dataProvider?.setRows && rowData?.length) {
|
||||
if (dataProvider?.setRows)
|
||||
dataProvider.setRows(rowData);
|
||||
}
|
||||
if (getGridRef) getGridRef.current = gridRef.current;
|
||||
const gridView = gridRef.current?.gridView ?? grid;
|
||||
if (!gridView?.columnByName) return;
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
"use client";
|
||||
|
||||
import { type Dispatch, type SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
LoadingWithAnimation,
|
||||
} from "@common/components";
|
||||
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 type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
|
||||
|
||||
type VerificationResultGridRow = Record<string, string | number>;
|
||||
|
||||
const PRCS_YN_LABEL: Record<string, string> = {
|
||||
"0": "미통과",
|
||||
"1": "통과",
|
||||
"2": "예외처리",
|
||||
"3": "반려",
|
||||
};
|
||||
|
||||
const verificationResultColumnInfo: ColumnInfo = {
|
||||
columns: [
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
||||
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
|
||||
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
||||
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
|
||||
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false },
|
||||
{ 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", "mdfcnDt"],
|
||||
};
|
||||
|
||||
export interface VerificationResultHistoryDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
queryContext?: {
|
||||
sewerBzmnCd: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
statsCd: string;
|
||||
} | null;
|
||||
title?: string;
|
||||
emptyMessage?: string;
|
||||
}
|
||||
|
||||
export function VerificationResultHistoryDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
queryContext,
|
||||
title = "검증 결과 내역",
|
||||
emptyMessage = "표시할 검증 결과가 없습니다.",
|
||||
}: VerificationResultHistoryDialogProps) {
|
||||
const [normalEntries, setNormalEntries] = useState<VerificationResultReportItem[]>([]);
|
||||
const [imrEntries, setImrEntries] = useState<VerificationResultReportItem[]>([]);
|
||||
const [normalPageIndex, setNormalPageIndex] = useState(0);
|
||||
const [imrPageIndex, setImrPageIndex] = useState(0);
|
||||
const [normalPageCount, setNormalPageCount] = useState(1);
|
||||
const [imrPageCount, setImrPageCount] = useState(1);
|
||||
const [normalLoading, setNormalLoading] = useState(false);
|
||||
const [imrLoading, setImrLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setNormalEntries([]);
|
||||
setImrEntries([]);
|
||||
setNormalPageIndex(0);
|
||||
setImrPageIndex(0);
|
||||
setNormalPageCount(1);
|
||||
setImrPageCount(1);
|
||||
setNormalLoading(false);
|
||||
setImrLoading(false);
|
||||
setErrorMessage(null);
|
||||
}, []);
|
||||
|
||||
const fetchVerificationResultPage = useCallback(
|
||||
async (vrfcMthdCd: "NV" | "IMR", pageIndex: number) => {
|
||||
if (!queryContext) return null;
|
||||
const param: VerificationResultListRequest = {
|
||||
sewerBzmnCd: queryContext.sewerBzmnCd,
|
||||
ctpvNm: queryContext.ctpvNm,
|
||||
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);
|
||||
},
|
||||
[queryContext],
|
||||
);
|
||||
|
||||
const loadInitial = useCallback(async () => {
|
||||
if (!queryContext) {
|
||||
setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setNormalLoading(true);
|
||||
setImrLoading(true);
|
||||
setNormalPageIndex(0);
|
||||
setImrPageIndex(0);
|
||||
try {
|
||||
const [nvRes, imrRes] = await Promise.all([
|
||||
fetchVerificationResultPage("NV", 0),
|
||||
fetchVerificationResultPage("IMR", 0),
|
||||
]);
|
||||
setNormalEntries(nvRes?.data?.verifyResultList ?? []);
|
||||
setImrEntries(imrRes?.data?.verifyResultList ?? []);
|
||||
setNormalPageCount(nvRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
setImrPageCount(imrRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
} catch {
|
||||
setNormalEntries([]);
|
||||
setImrEntries([]);
|
||||
setNormalPageCount(1);
|
||||
setImrPageCount(1);
|
||||
setErrorMessage("검증 결과 내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setNormalLoading(false);
|
||||
setImrLoading(false);
|
||||
}
|
||||
}, [fetchVerificationResultPage, queryContext]);
|
||||
|
||||
const handleNormalPageChange = useCallback(
|
||||
async (pageIndex: number) => {
|
||||
if (pageIndex === normalPageIndex || !queryContext) return;
|
||||
setErrorMessage(null);
|
||||
setNormalLoading(true);
|
||||
try {
|
||||
const response = await fetchVerificationResultPage("NV", pageIndex);
|
||||
setNormalEntries(response?.data?.verifyResultList ?? []);
|
||||
setNormalPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setNormalPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("일반검증 내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setNormalLoading(false);
|
||||
}
|
||||
},
|
||||
[fetchVerificationResultPage, normalPageIndex, queryContext],
|
||||
);
|
||||
|
||||
const handleImrPageChange = useCallback(
|
||||
async (pageIndex: number) => {
|
||||
if (pageIndex === imrPageIndex || !queryContext) return;
|
||||
setErrorMessage(null);
|
||||
setImrLoading(true);
|
||||
try {
|
||||
const response = await fetchVerificationResultPage("IMR", pageIndex);
|
||||
setImrEntries(response?.data?.verifyResultList ?? []);
|
||||
setImrPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setImrPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("I-MR 검증내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setImrLoading(false);
|
||||
}
|
||||
},
|
||||
[fetchVerificationResultPage, imrPageIndex, queryContext],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
resetState();
|
||||
return;
|
||||
}
|
||||
void loadInitial();
|
||||
}, [loadInitial, open, resetState]);
|
||||
|
||||
const mapRows = (entries: VerificationResultReportItem[]): VerificationResultGridRow[] =>
|
||||
entries.map((item) => ({
|
||||
ctpvNm: item.ctpvNm,
|
||||
sggNm: item.sggNm,
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
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]);
|
||||
const imrRows = useMemo(() => mapRows(imrEntries), [imrEntries]);
|
||||
|
||||
const noopSetRows = ((_: SetStateAction<Record<string, string | number>[]>) => {}) as Dispatch<
|
||||
SetStateAction<Record<string, string | number>[]>
|
||||
>;
|
||||
|
||||
const renderGrid = (
|
||||
rows: VerificationResultGridRow[],
|
||||
pageIndex: number,
|
||||
pageCount: number,
|
||||
onPageIndexChange: (page: number) => void,
|
||||
loading: boolean,
|
||||
) => {
|
||||
if (!loading && rows.length === 0) {
|
||||
return <p className="p-6 text-center text-[1.3rem] text-muted-foreground">{emptyMessage}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative space-y-2">
|
||||
<DataGrid
|
||||
columnInfo={verificationResultColumnInfo}
|
||||
rowData={rows}
|
||||
setRowData={noopSetRows}
|
||||
isExcelImport={false}
|
||||
isExcelExport={false}
|
||||
showHeaderCheckbox={false}
|
||||
enableSort={false}
|
||||
/>
|
||||
<VerificationResultHistoryPagination
|
||||
pageCount={Math.max(pageCount, 1)}
|
||||
pageIndex={Math.max(pageIndex, 0)}
|
||||
onPageIndexChange={onPageIndexChange}
|
||||
className="mx-2 mb-2"
|
||||
/>
|
||||
{loading && (
|
||||
<div className="absolute inset-0 z-10 rounded-md [data-theme='black']:bg-transparent bg-white/40 flex items-center justify-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[80%]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{`${title} - ${queryContext?.statsYr} ${queryContext?.ctpvNm} ${queryContext?.sggNm}`}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="max-h-[85vh] space-y-5 overflow-auto">
|
||||
{errorMessage && (
|
||||
<p className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[1.2rem] text-destructive">
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
<section className="rounded-md border border-border">
|
||||
<h3 className="border-b border-border px-4 py-2 text-[1.35rem] font-semibold">일반검증 내역</h3>
|
||||
{renderGrid(normalRows, normalPageIndex, normalPageCount, handleNormalPageChange, normalLoading)}
|
||||
</section>
|
||||
<section className="rounded-md border border-border">
|
||||
<h3 className="border-b border-border px-4 py-2 text-[1.35rem] font-semibold">I-MR 검증내역</h3>
|
||||
{renderGrid(imrRows, imrPageIndex, imrPageCount, handleImrPageChange, imrLoading)}
|
||||
</section>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button type="button" color="gray">
|
||||
닫기
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
DoubleArrowLeftIcon,
|
||||
DoubleArrowRightIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { Button, cn } from "@common/components";
|
||||
|
||||
export type VerificationResultHistoryPaginationProps = {
|
||||
pageCount: number;
|
||||
pageIndex: number;
|
||||
onPageIndexChange: (pageIndex: number) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function VerificationResultHistoryPagination({
|
||||
pageCount,
|
||||
pageIndex: currentPage,
|
||||
onPageIndexChange,
|
||||
className,
|
||||
}: VerificationResultHistoryPaginationProps) {
|
||||
const maxVisiblePages = 10;
|
||||
let startPage = Math.max(0, currentPage - maxVisiblePages / 2);
|
||||
const endPage = Math.min(pageCount, startPage + maxVisiblePages);
|
||||
|
||||
if (endPage - startPage < maxVisiblePages) {
|
||||
startPage = Math.max(0, endPage - maxVisiblePages);
|
||||
}
|
||||
|
||||
const pageNumbers = Array(endPage - startPage)
|
||||
.fill(0)
|
||||
.map((_, i) => startPage + i);
|
||||
|
||||
const canPrevious = currentPage > 0;
|
||||
const canNext = pageCount > 0 && currentPage < pageCount - 1;
|
||||
|
||||
return (
|
||||
<div className={cn("mt-2 flex items-center justify-center gap-1 rounded-md border border-border/70 bg-muted/20 px-2 py-2", className)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="white"
|
||||
className="hidden size-8 p-0 lg:flex"
|
||||
onClick={() => onPageIndexChange(0)}
|
||||
disabled={!canPrevious}
|
||||
>
|
||||
<span className="sr-only">첫 페이지로</span>
|
||||
<DoubleArrowLeftIcon className="size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="white"
|
||||
className="size-8 p-0"
|
||||
onClick={() => onPageIndexChange(currentPage - 1)}
|
||||
disabled={!canPrevious}
|
||||
>
|
||||
<span className="sr-only">이전 페이지로</span>
|
||||
<ChevronLeftIcon className="size-4" />
|
||||
</Button>
|
||||
<div className="flex items-center justify-center text-sm font-medium">
|
||||
{pageNumbers.map((pageNumber) => (
|
||||
<Button
|
||||
key={pageNumber}
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"inline-block h-8 min-w-8 border border-transparent !bg-transparent px-2 py-1 text-foreground hover:!bg-accent",
|
||||
currentPage === pageNumber &&
|
||||
"!bg-primary !font-bold !text-primary-foreground hover:!bg-primary",
|
||||
)}
|
||||
onClick={() => onPageIndexChange(pageNumber)}
|
||||
>
|
||||
{pageNumber + 1}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="white"
|
||||
className="size-8 p-0"
|
||||
onClick={() => onPageIndexChange(currentPage + 1)}
|
||||
disabled={!canNext}
|
||||
>
|
||||
<span className="sr-only">다음 페이지로</span>
|
||||
<ChevronRightIcon className="size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="white"
|
||||
className="hidden size-8 p-0 lg:flex"
|
||||
onClick={() => onPageIndexChange(Math.max(0, pageCount - 1))}
|
||||
disabled={!canNext}
|
||||
>
|
||||
<span className="sr-only">마지막 페이지로</span>
|
||||
<DoubleArrowRightIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,16 +6,18 @@ import DataGrid from "@/components/module/DataGrid";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
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";
|
||||
@@ -30,7 +32,11 @@ import type {
|
||||
SewerageSupplyRateSaveRequest,
|
||||
SewerageSupplyRateStatusInfo,
|
||||
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { cancelSubmitRequest, SubmitExceptionRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import type {
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SubmitExceptionRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
@@ -45,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 }
|
||||
@@ -69,11 +101,17 @@ const SeweragePenetrationRateInp = () => {
|
||||
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
|
||||
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();
|
||||
|
||||
@@ -88,6 +126,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
});
|
||||
|
||||
// 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요.
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const { columns, columnLayout } = SeweragePenetrationRateInpColumn;
|
||||
const base = (isEnterable || isAdminInfo) ? columns : columns.map((col) => ({ ...col, editable: false as const }));
|
||||
@@ -212,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);
|
||||
@@ -271,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?.();
|
||||
|
||||
@@ -348,39 +387,62 @@ const SeweragePenetrationRateInp = () => {
|
||||
|
||||
const response = await statisticsInputSewSupplyRateApi.submitSewerageSupplyRate(requestData);
|
||||
if (response.status === 'success') {
|
||||
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' ? '4' : '2'),
|
||||
};
|
||||
setGlobalProgress(50);
|
||||
|
||||
if (response.data.finalStatus === '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' ? '4' : '2'),
|
||||
gen_verify_stts_cd: response.data.nvpassed ? '1' : '2',
|
||||
};
|
||||
|
||||
try {
|
||||
await statisticsAPi.callImrService(imrParam);
|
||||
} catch {
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
setIsLoading(false);
|
||||
alert('IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.');
|
||||
return;
|
||||
try {
|
||||
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);
|
||||
setVerificationResultDialogOpen(true);
|
||||
} catch (e) {
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
setIsLoading(false);
|
||||
const msg =
|
||||
e instanceof Error && e.message
|
||||
? e.message
|
||||
: 'IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.';
|
||||
alert(msg);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setGlobalProgress(100);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false);
|
||||
alert('제출되었습니다.');
|
||||
}
|
||||
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false);
|
||||
alert('제출되었습니다.');
|
||||
} else {
|
||||
alert(response.message || '데이터 제출 중 오류가 발생했습니다.');
|
||||
}
|
||||
} 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?.();
|
||||
@@ -465,23 +527,8 @@ const SeweragePenetrationRateInp = () => {
|
||||
const response = await statisticsInputSewSupplyRateApi.saveSewerageSupplyRate(requestData);
|
||||
|
||||
if (response.status === 'success') {
|
||||
// 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'),
|
||||
// };
|
||||
|
||||
// try {
|
||||
// await statisticsAPi.callImrService(imrParam);
|
||||
// } catch {
|
||||
// await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
// setIsLoading(false);
|
||||
// alert('IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgress(100);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
alert('저장되었습니다.');
|
||||
} else {
|
||||
@@ -493,7 +540,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
} catch {
|
||||
alert('데이터 저장 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
hideGlobalProgress();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -512,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);
|
||||
@@ -526,6 +573,14 @@ const SeweragePenetrationRateInp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openVerificationHistoryDialog = () => {
|
||||
if (!searchValues?.year || !searchValues?.sido || !searchValues?.bsnmCd || !statsCd) {
|
||||
alert("먼저 조회를 진행해주세요.");
|
||||
return;
|
||||
}
|
||||
setVerificationHistoryOpen(true);
|
||||
};
|
||||
|
||||
const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || '')
|
||||
? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL')
|
||||
: true;
|
||||
@@ -547,12 +602,11 @@ const SeweragePenetrationRateInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
{isAdminInfo && statusInfo && statusInfo.laeSbmsnSttsCd !== '0' && (
|
||||
<SubmitExceptionButton SubmitExceptionFunc={() => submitFunc(true)} />
|
||||
)}
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
/>
|
||||
<LastYearDataButton getLastYearDataFunc={() => getLastYearDataFunc(searchValues ?? {})} />
|
||||
<SubmitButton submitFunc={() => submitFunc(false)} />
|
||||
|
||||
</>
|
||||
)}
|
||||
{statusInfo?.laeSbmsnSttsCd === '2' && <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />}
|
||||
@@ -560,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>
|
||||
@@ -593,7 +647,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
return errors
|
||||
.map((e) => {
|
||||
const prefix = e.vrfcCd === 'IMR' ? 'IMR검증 - ' : '일반검증 - ';
|
||||
return `${prefix}${e.rsnExpln}`;
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
@@ -614,6 +668,25 @@ const SeweragePenetrationRateInp = () => {
|
||||
submitMode={isExceptionSubmitFlow ? 'exception' : 'normal'}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<VerificationResultHistoryDialog
|
||||
open={verificationHistoryOpen}
|
||||
onOpenChange={setVerificationHistoryOpen}
|
||||
queryContext={
|
||||
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
|
||||
? {
|
||||
sewerBzmnCd: searchValues.bsnmCd,
|
||||
ctpvNm: searchValues.sido,
|
||||
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
|
||||
statsYr: searchValues.year,
|
||||
statsCd,
|
||||
}
|
||||
: 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,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { ValueType } from "realgrid";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { type SewerageSupplyRateStatusInfo } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
|
||||
@@ -241,6 +242,17 @@ export type CellTooltipCallback = (
|
||||
params: CellTooltipCallbackParams,
|
||||
) => string | undefined;
|
||||
|
||||
/** 셀 편집 완료 시 호출되는 콜백 (RealGrid onEditRowChanged) */
|
||||
export type CellEditedCallbackParams = {
|
||||
itemIndex: number;
|
||||
fieldName: string;
|
||||
oldValue: string | number;
|
||||
newValue: string | number;
|
||||
};
|
||||
export type CellEditedCallback = (
|
||||
params: CellEditedCallbackParams,
|
||||
) => void;
|
||||
|
||||
export type DataGridProps = {
|
||||
columnInfo: ColumnInfo;
|
||||
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
|
||||
@@ -259,10 +271,14 @@ export type DataGridProps = {
|
||||
cellStyleCallback?: CellStyleCallback;
|
||||
/** 셀 hover 시 툴팁 문구 (예: verificationResult의 rsnExpln) */
|
||||
cellTooltipCallback?: CellTooltipCallback;
|
||||
/** 셀 편집 완료 시 호출 (RealGrid onEditRowChanged) */
|
||||
onCellEdited?: CellEditedCallback;
|
||||
/** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */
|
||||
getGridDataRef?: {
|
||||
current: (() => Record<string, string | number>[]) | null;
|
||||
};
|
||||
/** 외부에서 gridRef에 직접 접근하기 위한 ref (setRows/addRows/setValue 직접 호출용) */
|
||||
getGridRef?: React.MutableRefObject<RealGridReact | null>;
|
||||
onRowClick?: (rowData: Record<string, string | number>) => void; // 행 클릭 이벤트
|
||||
/** CheckBar 행 체크 변경 */
|
||||
onItemChecked?: (itemIndex: number, checked: boolean) => void;
|
||||
|
||||
+6
-5
@@ -7,7 +7,8 @@ import {
|
||||
type SewerageSupplyRateApiResponse,
|
||||
type CheckInputPeriodResponse,
|
||||
type SewerageSupplyRateSaveRequest,
|
||||
type SewerageSupplyRateSaveApiResponse,
|
||||
type SewerageSupplyRateSubmitResponse,
|
||||
type SewerageSupplyRateSaveResponse,
|
||||
} from "../../model/input/statistics-sewage-supply-rate-input-interface";
|
||||
|
||||
export class StatisticsInputSewSupplyRateApiService implements StatisticsInputSewSupplyRateService {
|
||||
@@ -26,16 +27,16 @@ export class StatisticsInputSewSupplyRateApiService implements StatisticsInputSe
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse> {
|
||||
const response = await stClient.post<SewerageSupplyRateSaveApiResponse>(
|
||||
public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveResponse> {
|
||||
const response = await stClient.post<SewerageSupplyRateSaveResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate/save`,
|
||||
params,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse> {
|
||||
const response = await stClient.post<SewerageSupplyRateSaveApiResponse>(
|
||||
public async submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSubmitResponse> {
|
||||
const response = await stClient.post<SewerageSupplyRateSubmitResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate/submit`,
|
||||
params,
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
SeweragePipeInputPipeDiameterResponse,
|
||||
SeweragePipeInputPipeTypeResponse,
|
||||
SeweragePipeInputRegionalFactStatusSaveRequest,
|
||||
SeweragePipeInputRegionalFactStatusSaveApiResponse,
|
||||
SeweragePipeInputRegionalFactStatusSaveResponse,
|
||||
SeweragePipeInputRegionalResponse,
|
||||
SeweragePipeRequestParams,
|
||||
StatisticsInputSewPipeService,
|
||||
@@ -82,8 +82,8 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async saveRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSaveRequest): Promise<SeweragePipeInputRegionalFactStatusSaveApiResponse> {
|
||||
const response = await stClient.post<SeweragePipeInputRegionalFactStatusSaveApiResponse>(
|
||||
public async saveRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSaveRequest): Promise<SeweragePipeInputRegionalFactStatusSaveResponse> {
|
||||
const response = await stClient.post<SeweragePipeInputRegionalFactStatusSaveResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/regional-facility-status/save`,
|
||||
param,
|
||||
);
|
||||
|
||||
@@ -2,17 +2,28 @@ import { stClient } from "@/shared/api/client";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
ApprovalRequest,
|
||||
ApprovalResponse,
|
||||
ApproveRequest,
|
||||
RejectRequest,
|
||||
StatisticsApprovalService,
|
||||
} from "../model/statistics-approval-interface";
|
||||
|
||||
class StatisticsApprovalApiService implements StatisticsApprovalService {
|
||||
public async approveStats(
|
||||
params: ApprovalRequest[],
|
||||
params: ApproveRequest[],
|
||||
): Promise<ApprovalResponse> {
|
||||
const response = await stClient.post<ApprovalResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.APPROVAL}?type=VRFC&action=APPROVE`,
|
||||
`${ENDPOINT.ST_SERVICE.APPROVAL}/approve?type=VRFC`,
|
||||
params,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async rejectStats(
|
||||
params: RejectRequest[],
|
||||
): Promise<ApprovalResponse> {
|
||||
const response = await stClient.post<ApprovalResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.APPROVAL}/reject?type=VRFC`,
|
||||
params,
|
||||
);
|
||||
return response.data;
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
type ReviewReturnSaveResponse,
|
||||
type ReviewSubmitRequest,
|
||||
type ReviewSubmitResponse,
|
||||
type VerificationResultListRequest,
|
||||
type VerificationResultListResponse,
|
||||
type SearchCondition,
|
||||
type Sgg,
|
||||
type StatisticsLogDetailResponse,
|
||||
@@ -22,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>> {
|
||||
@@ -68,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,
|
||||
@@ -145,6 +156,14 @@ export class StatisticsAPiService implements StatisticsService {
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getVerificationResultList(param: VerificationResultListRequest): Promise<VerificationResultListResponse> {
|
||||
const response = await stClient.get<VerificationResultListResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/results`,
|
||||
{ params: param },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const statisticsAPi = new StatisticsAPiService();
|
||||
export const statisticsApi = new StatisticsApiService();
|
||||
|
||||
@@ -64,12 +64,13 @@ export {
|
||||
export { verificationHistoryColumns } from "./model/statistics-analysis/verification-history-columns";
|
||||
export { getVerificationListColumns } from "./model/statistics-analysis/verification-list-columns";
|
||||
export {
|
||||
type ApprovalRequest,
|
||||
type ApprovalResponse,
|
||||
type ApproveRequest,
|
||||
type RejectRequest,
|
||||
} from "./model/statistics-approval-interface";
|
||||
export {
|
||||
statisticsVerificationDetailColumnInfo,
|
||||
statisticsVerificationSummaryColumnInfo,
|
||||
statisticsVerificationReportColumnInfo,
|
||||
statisticsVerificationResultColumnInfo,
|
||||
} from "./model/statistics-verification-report/statistics-verification-report-columns";
|
||||
export {
|
||||
type StatisticsVerificationFilterState,
|
||||
|
||||
+6
-5
@@ -1,5 +1,3 @@
|
||||
import type { SewerageSupplyRateSaveApiResponse } from "./statistics-sewage-supply-rate-input-interface";
|
||||
|
||||
export interface SeweragePipeRequestParams {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
@@ -119,8 +117,11 @@ export interface SeweragePipeInputRegionalFactStatusSaveRequest {
|
||||
saveList: SeweragePipeInputRegionalFactStatusSaveItem[];
|
||||
}
|
||||
|
||||
/** 지역별 시설현황 저장 API 응답 — 보급률 저장과 동일 래핑 */
|
||||
export type SeweragePipeInputRegionalFactStatusSaveApiResponse = SewerageSupplyRateSaveApiResponse;
|
||||
export interface SeweragePipeInputRegionalFactStatusSaveResponse {
|
||||
data: string;
|
||||
message?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
/** 시설별 설치 현황 — 목록 항목 */
|
||||
export interface SeweragePipeInputFactInstallStatusItem {
|
||||
@@ -519,5 +520,5 @@ export interface StatisticsInputSewPipeService {
|
||||
getFacilityAdminAreaList(param: SeweragePipeFacilityAdminAreaListRequest): Promise<SeweragePipeFacilityAdminAreaListResponse>;
|
||||
saveRegionalFactStatus(
|
||||
param: SeweragePipeInputRegionalFactStatusSaveRequest,
|
||||
): Promise<SeweragePipeInputRegionalFactStatusSaveApiResponse>;
|
||||
): Promise<SeweragePipeInputRegionalFactStatusSaveResponse>;
|
||||
}
|
||||
+16
-6
@@ -36,6 +36,8 @@ export interface SewerageSupplyRateCommonInfo {
|
||||
export interface VerificationResultItem {
|
||||
vrfcCd: string;
|
||||
rsnExpln: string;
|
||||
nowVlCn: string;
|
||||
compVlCn: string;
|
||||
}
|
||||
|
||||
/** 필드명 → 검증 결과 배열. 예: { popltnSm: [{ type, rsn }], wstrPrmTrt: [...] } */
|
||||
@@ -150,12 +152,20 @@ export interface SewerageSupplyRateSaveRequest {
|
||||
superAdmin?: string;
|
||||
}
|
||||
|
||||
export interface SewerageSupplyRateSaveData {
|
||||
httpStatus?: number;
|
||||
code?: string;
|
||||
export interface SewerageSupplyRateSaveResponse {
|
||||
data: string;
|
||||
message?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export type SewerageSupplyRateSaveApiResponse = ApiResponse<SewerageSupplyRateSaveData>;
|
||||
export interface SewerageSupplyRateSubmitResponse {
|
||||
data: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
}
|
||||
message?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
// 하수도 통계 입력 기간 조회
|
||||
export interface CheckInputPeriodResponse {
|
||||
@@ -167,6 +177,6 @@ export interface CheckInputPeriodResponse {
|
||||
export interface StatisticsInputSewSupplyRateService {
|
||||
getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise<SewerageSupplyRateApiResponse>;
|
||||
checkInputPeriod(params: string): Promise<CheckInputPeriodResponse>;
|
||||
saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse>;
|
||||
submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse>;
|
||||
saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveResponse>;
|
||||
submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSubmitResponse>;
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
export interface ApprovalRequest {
|
||||
export interface ApproveRequest {
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
mdfrId: string;
|
||||
rjctOpnnCn: string;
|
||||
lastCfmtnPicNm: string;
|
||||
lastCfmtnPicTelNo: string;
|
||||
lastCfmtnPicDeptNm: string;
|
||||
lastCfmtnPicJbgdNm: string;
|
||||
lastCfmtnPicNm?: string;
|
||||
lastCfmtnPicTelNo?: string;
|
||||
lastCfmtnPicDeptNm?: string;
|
||||
lastCfmtnPicJbgdNm?: string;
|
||||
}
|
||||
|
||||
export interface RejectRequest {
|
||||
vrfcSn: number;
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
mdfrId: string;
|
||||
rjctOpnnCn?: string;
|
||||
}
|
||||
|
||||
export interface ApprovalResponse {
|
||||
@@ -18,5 +26,6 @@ export interface ApprovalResponse {
|
||||
}
|
||||
|
||||
export interface StatisticsApprovalService {
|
||||
approveStats(params: ApprovalRequest[]): Promise<ApprovalResponse>;
|
||||
approveStats(params: ApproveRequest[]): Promise<ApprovalResponse>;
|
||||
rejectStats(params: RejectRequest[]): Promise<ApprovalResponse>;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,91 @@ export interface ImrRequest {
|
||||
stats_cd: string;
|
||||
stats_yr: string;
|
||||
lae_sbmsn_stts_cd: string;
|
||||
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;
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsCd?: string;
|
||||
/** 검증 방법 코드 (예: IMR) */
|
||||
vrfcMthdCd?: string;
|
||||
/** 처리여부 코드 목록, 콤마 구분 */
|
||||
prcsYnList?: string;
|
||||
pbcpAprvYn?: string;
|
||||
page?: number;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
/** 검증 결과 리포트 목록 한 행 (`verifyResultList` 원소) */
|
||||
export interface VerificationResultReportItem {
|
||||
statsYr: string;
|
||||
statsCd: string;
|
||||
statsNm: string;
|
||||
sewerBzmnCd: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstNm: string;
|
||||
vrfcSn: number;
|
||||
vrfcMthdCd: string;
|
||||
vrfcMthdNm: string;
|
||||
vrfcTrgtNm: string;
|
||||
regDt: string;
|
||||
rsnExpln: string;
|
||||
nowVlCn: string;
|
||||
compVlCn: string | null;
|
||||
prcsYn: string;
|
||||
prcsOpnnExpln: string | null;
|
||||
prcrId: string | null;
|
||||
rjctOpnnCn: string | null;
|
||||
pbcpAprvYn: string;
|
||||
mdfrId: string | null;
|
||||
mdfcnDt: string;
|
||||
}
|
||||
|
||||
export interface VerificationResultListPageInfo {
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
currentPage: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface VerificationResultListData {
|
||||
verifyResultList: VerificationResultReportItem[];
|
||||
pageInfo: VerificationResultListPageInfo;
|
||||
}
|
||||
|
||||
/** 검증 결과 리포트 조회 API 응답 */
|
||||
export interface VerificationResultListResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: VerificationResultListData;
|
||||
}
|
||||
|
||||
export interface SubmitExceptionRequest {
|
||||
@@ -188,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>;
|
||||
@@ -196,4 +281,6 @@ export interface StatisticsService {
|
||||
getReviewOpinionDetail(param: getReviewOpinionDetailRequest): Promise<getReviewOpinionDetailResponse>;
|
||||
saveReviewReturn(param: ReviewReturnSaveRequest): Promise<ReviewReturnSaveResponse>;
|
||||
submitReview(param :ReviewSubmitRequest): Promise<ReviewSubmitResponse>;
|
||||
getVerificationResultList(param: VerificationResultListRequest): Promise<VerificationResultListResponse>;
|
||||
getImrStatus(param: string): Promise<ImrStatusResponse>;
|
||||
}
|
||||
+15
-3
@@ -2,7 +2,7 @@ import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
|
||||
const READ_ONLY_STYLE = "rg-cell-readonly";
|
||||
|
||||
export const statisticsVerificationSummaryColumnInfo: ColumnInfo = {
|
||||
export const statisticsVerificationReportColumnInfo: ColumnInfo = {
|
||||
columns: [
|
||||
{
|
||||
name: "ctpvNm",
|
||||
@@ -111,19 +111,21 @@ export const statisticsVerificationSummaryColumnInfo: ColumnInfo = {
|
||||
],
|
||||
};
|
||||
|
||||
export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
export const statisticsVerificationResultColumnInfo: ColumnInfo = {
|
||||
columns: [
|
||||
{
|
||||
name: "ctpvNm",
|
||||
fieldName: "ctpvNm",
|
||||
header: { text: "시도" },
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "sggNm",
|
||||
fieldName: "sggNm",
|
||||
header: { text: "시군구" },
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "statsNm",
|
||||
@@ -131,6 +133,7 @@ export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
header: { text: "지표" },
|
||||
width: 140,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "vrfcTrgtNm",
|
||||
@@ -138,6 +141,7 @@ export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
header: { text: "세부항목" },
|
||||
width: 180,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "regDt",
|
||||
@@ -145,6 +149,7 @@ export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
header: { text: "검증날짜" },
|
||||
width: 150,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "rsnExpln",
|
||||
@@ -152,6 +157,7 @@ export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
header: { text: "오류내역" },
|
||||
width: 200,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "nowVlCn",
|
||||
@@ -159,6 +165,7 @@ export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
header: { text: "입력값" },
|
||||
width: 90,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "compVlCn",
|
||||
@@ -166,6 +173,7 @@ export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
header: { text: "비교값" },
|
||||
width: 90,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "prcsYn",
|
||||
@@ -173,6 +181,7 @@ export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
header: { text: "상태" },
|
||||
width: 90,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "prcsOpnnExpln",
|
||||
@@ -180,13 +189,14 @@ export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
header: { text: "예외사유" },
|
||||
width: 170,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "rjctOpnnCn",
|
||||
fieldName: "rjctOpnnCn",
|
||||
header: { text: "반려의견" },
|
||||
width: 170,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
name: "pbcpAprvYn",
|
||||
@@ -194,12 +204,14 @@ export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
|
||||
header: { text: "승인여부" },
|
||||
width: 90,
|
||||
styleName: READ_ONLY_STYLE,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "vrfcSn",
|
||||
fieldName: "vrfcSn",
|
||||
header: { text: "vrfcSn" },
|
||||
visible: false,
|
||||
editable: false,
|
||||
},
|
||||
],
|
||||
columnLayout: [
|
||||
|
||||
@@ -73,7 +73,7 @@ export const ENDPOINT = {
|
||||
INPUTCOMMON: "/st-input-service/api/v1/common",
|
||||
INPUTVERIFY: "/st-input-service/api/v1/verify",
|
||||
TOTAL: "/st-management-service/api/v1/stats",
|
||||
APPROVAL: "/st-management-service/api/stats/approval",
|
||||
APPROVAL: "/st-management-service/api/stats",
|
||||
},
|
||||
STATVERIFY_SERVICE: {
|
||||
VRFCTGT_MNG: "/statverify-service/api/v1/vrfc-trgt-mng",
|
||||
|
||||
@@ -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