Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03a08a320a | ||
|
|
f2ec9327fd | ||
|
|
5331e10c96 | ||
|
|
de8971bea6 | ||
|
|
a7fd6a8e16 | ||
|
|
8039ab8281 | ||
|
|
794691f05e | ||
|
|
e58b84a01b | ||
|
|
3d51f003c0 | ||
|
|
b5a99e5f59 | ||
|
|
afca685fd4 | ||
|
|
261941452a | ||
|
|
8576ef83b5 | ||
|
|
e80b8953a0 | ||
|
|
e8b309460e | ||
|
|
37ac927c45 | ||
|
|
bf9a34cbe8 | ||
|
|
18a11ab5fa | ||
|
|
214ff1a435 | ||
|
|
d7cc579335 | ||
|
|
9b53c1ae08 | ||
|
|
64ed53cf4d | ||
|
|
043fd3d4a8 | ||
|
|
6dd09971f1 | ||
|
|
23424b5eee | ||
|
|
3f190dfff6 | ||
|
|
6cc02da7a4 | ||
|
|
c87ccf8788 | ||
|
|
8e757ab9db | ||
|
|
de7e66afb4 | ||
|
|
c981b83284 | ||
|
|
927bb7cb3a | ||
|
|
725976b5c0 | ||
|
|
cdbe6a855f | ||
|
|
a1c96e9e98 | ||
|
|
1488805242 | ||
|
|
5b12f81b8f | ||
|
|
df24e7c2a8 | ||
|
|
3b5fee047b | ||
|
|
181730bcde | ||
|
|
678af2f06e | ||
|
|
82d67bb4c6 | ||
|
|
85ef1805e5 | ||
|
|
26f64988ef | ||
|
|
29e39f98af | ||
|
|
5192f3e3c0 | ||
|
|
83de317308 | ||
|
|
84b1fd07ae | ||
|
|
a88f8ab33e | ||
|
|
c67b5844b6 |
@@ -1,7 +1,7 @@
|
||||
import "@/app/styles/base.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "유역하수도 통합 정보플랫폼",
|
||||
title: "하수도통계 관리시스템",
|
||||
};
|
||||
|
||||
export default function AuthLayout({
|
||||
|
||||
@@ -97,7 +97,7 @@ function WithBaseLayoutInner({ children }: { children: React.ReactNode }) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!getToken(CLAIM_NAME)) return;
|
||||
void statisticsApi.refreshCommonCodes().catch(() => {
|
||||
void statisticsApi.refreshAllCommonCodes().catch(() => {
|
||||
// 캐시 동기화 실패는 화면 진입을 막지 않음
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import { App } from "@/app";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "유역하수도 통합 정보플랫폼",
|
||||
title: "하수도통계 관리시스템",
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { COOKIE_DOMAIN, REFRESH_TOKEN } from "@/shared/config";
|
||||
|
||||
export async function POST() {
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.delete({ name: REFRESH_TOKEN, domain: COOKIE_DOMAIN });
|
||||
return NextResponse.json({ message: "Logged out" }, { status: 200 });
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
|
||||
import "@/app/styles/base.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "하수도통계 관리시스템",
|
||||
description: "페이지가 존재하지 않습니다. 관리자에게 문의하여 주십시오.",
|
||||
};
|
||||
|
||||
export default function GlobalNotFound() {
|
||||
return (
|
||||
<html lang="ko">
|
||||
<body>
|
||||
<main className="flex min-h-screen flex-col items-center justify-center bg-[#f0f4f8] px-4 py-16">
|
||||
<div className="mb-16 text-center">
|
||||
<h1 className="text-[3.2rem] font-bold leading-snug tracking-tight">
|
||||
<span className="text-primary">
|
||||
하수도통계 관리시스템
|
||||
</span>
|
||||
에 오신 것을 환영합니다.
|
||||
</h1>
|
||||
<p className="mt-6 text-[2rem] text-[#666]">
|
||||
오류가 발생하였습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="card card-border flex w-full max-w-[72rem] flex-col items-center !rounded-2xl !py-16">
|
||||
<Image
|
||||
src="/images/magnifying-glass-with-warning.png"
|
||||
alt=""
|
||||
width={280}
|
||||
height={280}
|
||||
className="size-[28rem] object-contain"
|
||||
/>
|
||||
<p className="mt-4 text-[2.4rem] font-bold text-[#444]">
|
||||
페이지가 존재하지 않습니다.
|
||||
</p>
|
||||
<p className="mt-2 text-[1.6rem] text-[#666]">
|
||||
관리자에게 문의하여 주십시오
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,9 @@ const subDomain =
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
experimental: {
|
||||
globalNotFound: true,
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"chart.js": "^4.4.8",
|
||||
"chartjs-plugin-zoom": "^2.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"decimal.js": "^10.6.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.474.0",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 483 KiB |
@@ -1,12 +1,14 @@
|
||||
import { cookies } from "next/headers";
|
||||
import Script from "next/script";
|
||||
|
||||
import "@fontsource-variable/noto-sans-kr";
|
||||
|
||||
import { AppProvider } from "./providers/app-provider";
|
||||
|
||||
import "./styles/base.css";
|
||||
import "./styles/components.css";
|
||||
import "@fontsource-variable/noto-sans-kr";
|
||||
import "realgrid/dist/realgrid-style.css";
|
||||
import "../components/css/DataGrid.css";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import { AppProvider } from "./providers/app-provider";
|
||||
import Script from "next/script";
|
||||
const App = async ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
||||
const theme = (await cookies()).get("site-theme-key");
|
||||
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { MultiBackend, getBackendOptions } from "@minoru/react-dnd-treeview";
|
||||
|
||||
import { ConfirmProvider } from "@frontend-opensource/use-react-hooks";
|
||||
import { getBackendOptions,MultiBackend } from "@minoru/react-dnd-treeview";
|
||||
import { AlertDialog as RsoneAlertDialog } from "@rsone/components";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import { ConfirmProvider } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { PromptProvider, StoreProvider } from "@/shared/providers";
|
||||
import { AlertDialog } from "@/shared/ui/alert-dialog";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { GlobalProgressOverlay } from "@/shared/ui/global-progress-overlay";
|
||||
import { PromptDialog } from "@/shared/ui/prompt-dialog";
|
||||
import { SelectPromptDialog } from "@/shared/ui/select-prompt-dialog";
|
||||
import { Toaster } from "@/shared/ui/sonner";
|
||||
import { AuthWrapper } from "@/entities/auth";
|
||||
import { MenuWrapper } from "@/entities/menu";
|
||||
import { Toaster } from "@/shared/ui/sonner";
|
||||
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";
|
||||
|
||||
const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button } from "@common/components";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { Button } from "@common/components";
|
||||
|
||||
interface LastYearDataButtonProps {
|
||||
getLastYearDataFunc: () => void;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Checkbox, buttonVariants, cn } from "@common/components";
|
||||
import type React from "react";
|
||||
|
||||
import { buttonVariants, Checkbox, cn } from "@common/components";
|
||||
|
||||
interface NotApplicableCheckButtonProp {
|
||||
isCheck: boolean;
|
||||
setIsCheck: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
@@ -2,15 +2,17 @@ import { Button } from "@common/components";
|
||||
|
||||
interface SubmitButtonProps {
|
||||
submitFunc: () => void;
|
||||
/** 버튼 문구 (기본: 제출) */
|
||||
text?: string;
|
||||
}
|
||||
|
||||
const SubmitButton = ({ submitFunc }: SubmitButtonProps) => {
|
||||
const SubmitButton = ({ submitFunc, text = "제출" }: SubmitButtonProps) => {
|
||||
return (
|
||||
<Button className="h-[3.2rem] w-[96px]" onClick={submitFunc}>
|
||||
<Button className="h-[3.2rem] min-w-[96px] px-4" onClick={submitFunc}>
|
||||
<i className="diveicon di-upload" />
|
||||
<span>제출</span>
|
||||
<span>{text}</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubmitButton;
|
||||
export default SubmitButton;
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
@tailwind components;
|
||||
|
||||
/* RealGrid 기본은 white-space: nowrap → max-width만 주면 잘림. 너비 초과 시에만 줄바꿈 */
|
||||
.rg-tooltip {
|
||||
max-width: 60rem;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* RealGrid Excel export — applyDynamicStyles가 class background-color를 읽을 수 있도록 */
|
||||
.rg-cell-rowtype-biz-sum {
|
||||
background-color: #ffe9cf !important;
|
||||
}
|
||||
.rg-cell-rowtype-summary {
|
||||
background-color: #feee85 !important;
|
||||
}
|
||||
.rg-cell-rowtype-other {
|
||||
background-color: #fff7bf !important;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell {
|
||||
@apply !bg-white;
|
||||
@@ -8,6 +25,15 @@
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly {
|
||||
@apply !bg-[#f0f0f0] !text-[#666];
|
||||
}
|
||||
/* 고정 컬럼 — readonly와 동일한 화면 스타일 (엑셀 export 시 exportStyleName에서 제외) */
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly,
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-readonly {
|
||||
@apply !bg-[#f0f0f0] !text-[#666];
|
||||
}
|
||||
.rg-grid .rg-fixed-body .rg-table .rg-data-row td.rg-cell-readonly,
|
||||
.rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-readonly {
|
||||
@apply !bg-[#f0f0f0] !text-[#666];
|
||||
}
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-rowtype-curr {
|
||||
@apply !bg-white !text-[#333];
|
||||
}
|
||||
@@ -17,6 +43,15 @@
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-verify-error-imr {
|
||||
@apply !bg-[#fff7bf] !text-[#b45309];
|
||||
}
|
||||
/* 총계 footer 검증 오류 (commonInfo.verificationResult) */
|
||||
.rg-grid .rg-footer .rg-table .rg-footer-cell.rg-cell-verify-error,
|
||||
.rg-grid .rg-footers .rg-table .rg-footer-cell.rg-cell-verify-error {
|
||||
@apply !bg-[#ffe6e6] !text-[#c00];
|
||||
}
|
||||
.rg-grid .rg-footer .rg-table .rg-footer-cell.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-footers .rg-table .rg-footer-cell.rg-cell-verify-error-imr {
|
||||
@apply !bg-[#fff7bf] !text-[#b45309];
|
||||
}
|
||||
/* 검증결과 내역: 처리여부(prcsYn 0·4 미처리 등) */
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-vrf-prcs-bad {
|
||||
@apply !font-semibold !text-[#dc2626];
|
||||
@@ -399,6 +434,13 @@
|
||||
background-color: #ffe6e6 !important;
|
||||
color: #c00 !important;
|
||||
}
|
||||
.rg-grid .rg-footer .rg-footer-cell.rg-cell-verify-error.rg-cell-verify-error,
|
||||
.rg-grid .rg-footers .rg-footer-cell.rg-cell-verify-error.rg-cell-verify-error,
|
||||
.rg-grid .rg-footer .rg-table .rg-footer-cell.rg-cell-verify-error.rg-cell-verify-error,
|
||||
.rg-grid .rg-footers .rg-table .rg-footer-cell.rg-cell-verify-error.rg-cell-verify-error {
|
||||
background-color: #ffe6e6 !important;
|
||||
color: #c00 !important;
|
||||
}
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-fixed-body .rg-data-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
@@ -406,6 +448,13 @@
|
||||
background-color: #fff7bf !important;
|
||||
color: #b45309 !important;
|
||||
}
|
||||
.rg-grid .rg-footer .rg-footer-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-footers .rg-footer-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-footer .rg-table .rg-footer-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-footers .rg-table .rg-footer-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr {
|
||||
background-color: #fff7bf !important;
|
||||
color: #b45309 !important;
|
||||
}
|
||||
|
||||
/* dark theme에서도 검증 오류 색을 유지하고 싶다면 동일 색 유지(가독성 우선) */
|
||||
[data-theme="black"] .rg-grid .rg-body .rg-data-cell.rg-cell-verify-error.rg-cell-verify-error,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useRef, useEffect, useLayoutEffect } from "react"
|
||||
import { ValueType } from "realgrid"
|
||||
import { useEffect, useLayoutEffect,useRef } from "react"
|
||||
|
||||
import { ValueType } from "realgrid"
|
||||
import RealGrid, { GridFitStyle } from "realgrid"
|
||||
import { RealGridReact, RGDataColumn } from "realgrid-react"
|
||||
import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback, CellStyleCallback, CellStyleCallbackResult, CellStyleCallbackParams } from "../type/DataGrid.type"
|
||||
|
||||
import type { CellStyleCallback, CellStyleCallbackParams,CellStyleCallbackResult, DataGridProps, FooterItemWithCallback, GridViewWithSetColumns, RealGridColumnInfo } from "../type/DataGrid.type"
|
||||
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
|
||||
import { wrapWithFormatTruncate } from "@/components/util/grid/decimalNumber"
|
||||
|
||||
function mergeCellStyleCallbackResult(
|
||||
baseStyleName: string | undefined,
|
||||
@@ -30,7 +33,7 @@ declare module "realgrid" {
|
||||
setOptions(options: {
|
||||
footer?: { visible?: boolean };
|
||||
summaryMode?: 'aggregate' | 'statistical' | 'none';
|
||||
checkBar?: { visible?: boolean };
|
||||
checkBar?: { visible?: boolean; checkableOnly?: boolean };
|
||||
}): void;
|
||||
}
|
||||
}
|
||||
@@ -47,10 +50,11 @@ type GridViewWithCheckApi = GridViewWithSetColumns & {
|
||||
checkItem?: (itemIndex: number, checked: boolean, exclusive?: boolean) => void;
|
||||
checkRow?: (dataRow: number, checked: boolean, exclusive?: boolean) => void;
|
||||
checkAll?: (checked: boolean) => void;
|
||||
setCheckable?: (itemIndex: number, checkable: boolean) => void;
|
||||
};
|
||||
|
||||
/** columnInfo → RealGrid DataField 배열 (computed → valueCallback 포함, setFields용) */
|
||||
function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueType; computed?: import("../type/DataGrid.type").ComputedFieldConfig }[]) {
|
||||
function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueType; dataFormat?: string; computed?: import("../type/DataGrid.type").ComputedFieldConfig }[]) {
|
||||
return columns.map((col) => {
|
||||
const field: { fieldName: string; dataType: string; valueCallback?: (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number } = {
|
||||
fieldName: col.fieldName,
|
||||
@@ -66,7 +70,7 @@ function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueT
|
||||
const hasSumOfMaxGroups = "groups" in col.computed && Array.isArray(col.computed.groups) && col.computed.groups.length > 0;
|
||||
const hasSumPerTenThousand = col.computed.type === "sumPerTenThousand" && "numeratorFields" in col.computed && col.computed.numeratorFields.length > 0 && "denominatorField" in col.computed && Boolean(col.computed.denominatorField);
|
||||
if (hasFields || hasNumeratorDenominator || hasMaxOfFieldAndSum || hasDifferenceOfSums || hasHalfSumOfSums || hasSumOfTwoHalfSumOfSums || hasSumOfMaxGroups || hasSumPerTenThousand) {
|
||||
field.valueCallback = getComputedValueCallback(col.computed) as (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number;
|
||||
field.valueCallback = wrapWithFormatTruncate(getComputedValueCallback(col.computed), col.dataFormat);
|
||||
}
|
||||
}
|
||||
return field;
|
||||
@@ -136,6 +140,7 @@ const CompactDataGrid = ({
|
||||
onRowClick,
|
||||
onItemChecked,
|
||||
onItemAllChecked,
|
||||
checkableCallback,
|
||||
compactFullWidth = false,
|
||||
compactGridHeightClass = "h-[55vh]",
|
||||
headerHeight = 60,
|
||||
@@ -145,9 +150,11 @@ const CompactDataGrid = ({
|
||||
const columnsRef = useRef(columns);
|
||||
const rowDataRef = useRef<Record<string, string | number>[]>(rowData);
|
||||
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(cellStyleCallback);
|
||||
const checkableCallbackRef = useRef<DataGridProps["checkableCallback"]>(checkableCallback);
|
||||
columnsRef.current = columns;
|
||||
rowDataRef.current = rowData;
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
checkableCallbackRef.current = checkableCallback;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!cellStyleCallback || !gridRef.current?.gridView) return;
|
||||
@@ -176,8 +183,10 @@ const CompactDataGrid = ({
|
||||
try {
|
||||
checkGrid.checkAll?.(false);
|
||||
rowData.forEach((row, itemIndex) => {
|
||||
const checkable = checkableCallbackRef.current?.(row, itemIndex) ?? true;
|
||||
checkGrid.setCheckable?.(itemIndex, checkable);
|
||||
const checked = checkByRowFlag((row as Record<string, unknown>).__checked);
|
||||
if (!checked) return;
|
||||
if (!checked || !checkable) return;
|
||||
if (checkGrid.checkItem) {
|
||||
checkGrid.checkItem(itemIndex, true, false);
|
||||
return;
|
||||
@@ -274,7 +283,7 @@ const CompactDataGrid = ({
|
||||
|
||||
// 헤더 체크박스 표시 여부 설정
|
||||
gridView.setOptions({
|
||||
checkBar: { visible: showHeaderCheckbox },
|
||||
checkBar: { visible: showHeaderCheckbox, checkableOnly: true },
|
||||
rowIndicator: { visible: false },
|
||||
});
|
||||
|
||||
@@ -306,6 +315,7 @@ const CompactDataGrid = ({
|
||||
if (fixedColumns !== undefined && fixedColumns > 0) {
|
||||
gridView.setFixedOptions({
|
||||
colCount: fixedColumns,
|
||||
resizable: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -496,6 +506,7 @@ const CompactDataGrid = ({
|
||||
compactFullWidth,
|
||||
compactGridHeightClass,
|
||||
headerHeight,
|
||||
checkableCallback,
|
||||
]);
|
||||
|
||||
// 행 클릭 이벤트
|
||||
@@ -524,6 +535,17 @@ const CompactDataGrid = ({
|
||||
|
||||
if (onItemChecked) {
|
||||
gridView.onItemChecked = (_grid, itemIndex, checked) => {
|
||||
const row = rowDataRef.current[itemIndex];
|
||||
if (
|
||||
row &&
|
||||
checkableCallbackRef.current &&
|
||||
!checkableCallbackRef.current(row, itemIndex)
|
||||
) {
|
||||
if (checked) {
|
||||
(_grid as GridViewWithCheckApi).checkItem?.(itemIndex, false, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
onItemChecked(itemIndex, checked);
|
||||
};
|
||||
} else {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ import { type Dispatch, type MutableRefObject, type SetStateAction, useCallback,
|
||||
|
||||
import { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
@@ -29,6 +28,8 @@ import {
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
||||
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
@@ -56,8 +57,8 @@ type ExceptionHistoryTabKey = "NV" | "IMR" | "HB";
|
||||
|
||||
const exceptionHistoryTabs: { value: ExceptionHistoryTabKey; label: string }[] = [
|
||||
{ value: "NV", label: "일반검증 내역" },
|
||||
{ value: "IMR", label: "I-MR 검증내역" },
|
||||
{ value: "HB", label: "HB 검증내역" },
|
||||
{ value: "IMR", label: "시계열 검증내역" },
|
||||
{ value: "HB", label: "모집단 검증내역" },
|
||||
];
|
||||
|
||||
const TITLE = "예외처리 내역 조회";
|
||||
@@ -69,7 +70,7 @@ export type ExceptionRejectSubmitItem = {
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
mdfrId: string;
|
||||
mdfrUuid: string;
|
||||
rjctOpnnCn?: string;
|
||||
};
|
||||
|
||||
@@ -83,8 +84,8 @@ function prcsYnCodeToNumber(raw: string | number | undefined): number | undefine
|
||||
}
|
||||
|
||||
function exceptionPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined {
|
||||
if (fieldName !== "prcsYnNm") return undefined;
|
||||
const code = prcsYnCodeToNumber(row.prcsYn);
|
||||
if (fieldName !== "vrfcSttsNm") return undefined;
|
||||
const code = prcsYnCodeToNumber(row.vrfcSttsCd);
|
||||
if (code === 0 || code === 4) return "rg-cell-vrf-prcs-bad";
|
||||
return "rg-cell-vrf-prcs-ok";
|
||||
}
|
||||
@@ -95,10 +96,10 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
|
||||
{ name: "admdstNm", fieldName: "admdstNm", 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: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "stVrfRjtComNm", fieldName: "stVrfRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "nowCn", fieldName: "nowCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "cmprCn", fieldName: "cmprCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "vrfcSttsNm", fieldName: "vrfcSttsNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "statsVrfcRjtComNm", fieldName: "statsVrfcRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false },
|
||||
{ name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
@@ -111,7 +112,7 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
|
||||
width: 104,
|
||||
styleName: "rg-cell-reject-mini-btn",
|
||||
},
|
||||
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
{ name: "vrfcSttsCd", fieldName: "vrfcSttsCd", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
{ name: "vrfcSn", fieldName: "vrfcSn", header: { text: "vrfcSn" }, editable: false, visible: false },
|
||||
],
|
||||
columnLayout: [
|
||||
@@ -119,16 +120,16 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
|
||||
"admdstNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYnNm",
|
||||
"stVrfRjtComNm",
|
||||
"nowCn",
|
||||
"cmprCn",
|
||||
"vrfcSttsNm",
|
||||
"statsVrfcRjtComNm",
|
||||
"prcsOpnnExpln",
|
||||
"rjctOpnnCn",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"individualReject",
|
||||
"prcsYn",
|
||||
"vrfcSttsCd",
|
||||
"vrfcSn",
|
||||
],
|
||||
};
|
||||
@@ -274,7 +275,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
setImrPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setImrPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("I-MR 검증내역 조회 중 오류가 발생했습니다.");
|
||||
setErrorMessage("시계열 검증내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setImrLoading(false);
|
||||
}
|
||||
@@ -293,7 +294,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
setHbPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setHbPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("HB 검증내역 조회 중 오류가 발생했습니다.");
|
||||
setErrorMessage("모집단 검증내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setHbLoading(false);
|
||||
}
|
||||
@@ -315,13 +316,13 @@ export function ExceptionProcessingHistoryDialog({
|
||||
admdstNm: item.admdstNm ?? item.sggNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYnNm: item.prcsYnNm ?? "-",
|
||||
stVrfRjtComNm: item.stVrfRjtComNm ?? "-",
|
||||
nowCn: item.nowCn ?? "-",
|
||||
cmprCn: item.cmprCn ?? "-",
|
||||
vrfcSttsNm: item.vrfcSttsNm ?? "-",
|
||||
statsVrfcRjtComNm: item.statsVrfcRjtComNm ?? "-",
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "-",
|
||||
rjctOpnnCn: item.rjctOpnnCn ?? "-",
|
||||
prcsYn: item.prcsYn ?? "",
|
||||
vrfcSttsCd: item.vrfcSttsCd ?? "",
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
individualReject: "개별 반려",
|
||||
@@ -400,7 +401,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
alert("사용자 정보를 불러오지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
const mdfrId = userInfo.userUnqId;
|
||||
const mdfrUuid = userInfo.userUnqId;
|
||||
const trimmedReason = rejectReasonText.trim();
|
||||
|
||||
const items: ReviewInputSaveRequest[] = pendingRejectVrfcSns.map((vrfcSn) => ({
|
||||
@@ -408,7 +409,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
statsYr: queryContext.statsYr,
|
||||
sewerBzmnCd: queryContext.sewerBzmnCd,
|
||||
statsCd: queryContext.statsCd,
|
||||
mdfrId,
|
||||
mdfrUuid,
|
||||
vrfcSn: String(vrfcSn),
|
||||
...(trimmedReason ? { rjctOpnnCn: trimmedReason } : {}),
|
||||
}));
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@common/components";
|
||||
|
||||
import SidoSggFacilityNameSearchForm from "./SidoSggFacilityNameSearchForm";
|
||||
import type { SearchFormValues } from "../type/SearchForm.type";
|
||||
|
||||
import SidoSggFacilityNameSearchForm from "./SidoSggFacilityNameSearchForm";
|
||||
|
||||
type GridTopActionButtonProps = {
|
||||
text: string;
|
||||
onClick?: (helpers: { openDialog: (title?: string) => void; closeDialog: () => void }) => void;
|
||||
|
||||
@@ -5,6 +5,11 @@ import { useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import {
|
||||
fetchSearchCondition,
|
||||
fetchSearchConditionInput,
|
||||
SEARCH_CONDITION_SWR_KEY,
|
||||
} from "@/shared/lib/use-search-condition-sido-list";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ApiResponse,
|
||||
@@ -23,28 +28,43 @@ import {
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
|
||||
import { doList, doNameList } from "@/components/util/column/input/DoList";
|
||||
|
||||
import {
|
||||
defaultSearchFormProps,
|
||||
type SearchFormClsfCd,
|
||||
type SearchFormProps,
|
||||
type SearchFormValues,
|
||||
} from "../type/SearchForm.type";
|
||||
import { doList } from "../util/column/input/DoList";
|
||||
|
||||
const getSidoListFetcher = async (
|
||||
key: [string, string],
|
||||
): Promise<ApiResponse<SearchCondition>> => {
|
||||
return statisticsApi.getSidoList({ instSeCd: key[1] });
|
||||
};
|
||||
function isDoListBsnm(bsnmCd: string | undefined): boolean {
|
||||
return Boolean(bsnmCd && doList.includes(bsnmCd));
|
||||
}
|
||||
|
||||
function isDoSido(sido?: { bsnmCd?: string; sido?: string }): boolean {
|
||||
if (!sido) return false;
|
||||
if (isDoListBsnm(sido.bsnmCd)) return true;
|
||||
return Boolean(sido.sido && doNameList.includes(sido.sido));
|
||||
}
|
||||
|
||||
function isAllGugunValue(gugun: string | undefined): boolean {
|
||||
const value = (gugun ?? "").trim();
|
||||
return !value || value === "ALL" || value === "전체";
|
||||
}
|
||||
|
||||
function firstConcreteSgg(list: Sgg[] | undefined): Sgg | undefined {
|
||||
return (list ?? []).find((sgg) => !isAllGugunValue(sgg.gugun));
|
||||
}
|
||||
|
||||
const getSggListFetcher = async (
|
||||
key: [string, string, string, string],
|
||||
key: [string, string, string, "Y" | "N"],
|
||||
): Promise<ApiResponse<Sgg[]>> => {
|
||||
const gugunSkipYn = doList.includes(key[3]) ? "N" : "Y";
|
||||
return statisticsApi.getSggList(key[1], {
|
||||
instSeCd: key[2],
|
||||
gugunSkipYn,
|
||||
});
|
||||
const [kind, sidoName, instSeCd, gugunSkipYn] = key;
|
||||
const params = { instSeCd, gugunSkipYn };
|
||||
if (kind === "sgg-all") {
|
||||
return statisticsApi.getSggAllList(sidoName, params);
|
||||
}
|
||||
return statisticsApi.getSggList(sidoName, params);
|
||||
};
|
||||
|
||||
function buildSearchFormValues(
|
||||
@@ -59,9 +79,15 @@ function buildSearchFormValues(
|
||||
: "ALL";
|
||||
|
||||
if (d?.sido) {
|
||||
const sidoItem = sidoList?.data?.sidoList?.find(
|
||||
(item) => item.sido === d.sido,
|
||||
);
|
||||
const gugun =
|
||||
d.gugun ||
|
||||
(sidoItem && !isDoSido(sidoItem) ? "ALL" : "");
|
||||
return {
|
||||
sido: d.sido,
|
||||
gugun: d.gugun || "",
|
||||
gugun,
|
||||
year: d.year || defaultYear.toString(),
|
||||
order: d.order || "",
|
||||
fctName: d.fctName || "",
|
||||
@@ -73,12 +99,28 @@ function buildSearchFormValues(
|
||||
};
|
||||
}
|
||||
|
||||
if (props.defaultRegionAll) {
|
||||
return {
|
||||
sido: "ALL",
|
||||
gugun: "ALL",
|
||||
year: d?.year || defaultYear.toString(),
|
||||
order: d?.order || "",
|
||||
fctName: d?.fctName || "",
|
||||
bsnmCd: "",
|
||||
isDetail: false,
|
||||
isSummary: d?.isSummary || "",
|
||||
clsfCd: initialClsfCd,
|
||||
capacityGbn: d?.capacityGbn || "ALL",
|
||||
};
|
||||
}
|
||||
|
||||
const first = sidoList?.data?.sidoList?.[0];
|
||||
const useFirstSido = (props.sido ?? defaultSearchFormProps.sido) && first;
|
||||
|
||||
return {
|
||||
sido: useFirstSido ? first.sido : "",
|
||||
gugun: "",
|
||||
gugun:
|
||||
useFirstSido && first && !isDoSido(first) ? "ALL" : "",
|
||||
year: d?.year || defaultYear.toString(),
|
||||
order: d?.order || "",
|
||||
fctName: d?.fctName || "",
|
||||
@@ -102,10 +144,15 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const defaultYear = currentYear - 1;
|
||||
const isInput = Boolean(props.isInput);
|
||||
const isDetailInputId = props.tabKey
|
||||
? `${props.tabKey}-isDetail`
|
||||
: "isDetail";
|
||||
const sidoListKind = isInput ? "input" : "default";
|
||||
|
||||
const { data: sidoList } = useSWR<ApiResponse<SearchCondition>>(
|
||||
["sido-list", instSeCd],
|
||||
getSidoListFetcher,
|
||||
SEARCH_CONDITION_SWR_KEY(instSeCd, sidoListKind),
|
||||
isInput ? fetchSearchConditionInput : fetchSearchCondition,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
@@ -132,6 +179,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
|
||||
// 최초 진입만 시도 API가 늦게 오는 경우
|
||||
useEffect(() => {
|
||||
if (props.defaultRegionAll) return;
|
||||
if (props.defaultValues?.sido) return;
|
||||
const first = sidoList?.data?.sidoList?.[0];
|
||||
if (!first || !mergedProps.sido) return;
|
||||
@@ -139,17 +187,33 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
prev.sido
|
||||
? prev
|
||||
: {
|
||||
...prev,
|
||||
sido: first.sido,
|
||||
gugun: "",
|
||||
bsnmCd: first.bsnmCd,
|
||||
},
|
||||
...prev,
|
||||
sido: first.sido,
|
||||
gugun: isDoSido(first) ? "" : "ALL",
|
||||
bsnmCd: first.bsnmCd,
|
||||
},
|
||||
);
|
||||
}, [sidoList, props.defaultValues?.sido, mergedProps.sido]);
|
||||
}, [
|
||||
sidoList,
|
||||
props.defaultValues?.sido,
|
||||
props.defaultRegionAll,
|
||||
mergedProps.sido,
|
||||
]);
|
||||
|
||||
const sggKey: [string, string, string, string] | null =
|
||||
values.sido && values.sido !== "ALL"
|
||||
? ["sgg-list", values.sido, instSeCd, values.bsnmCd || ""]
|
||||
const selectedSido = sidoList?.data?.sidoList?.find(
|
||||
(item) => item.sido === values.sido,
|
||||
);
|
||||
const gugunSkipYn = selectedSido?.gugunSkipYn ?? "Y";
|
||||
const useSggAllList =
|
||||
gugunSkipYn === "N" && !isDoListBsnm(selectedSido?.bsnmCd);
|
||||
const sggKey: [string, string, string, "Y" | "N"] | null =
|
||||
values.sido && values.sido !== "ALL" && sidoList?.data
|
||||
? [
|
||||
useSggAllList ? "sgg-all" : "sgg-list",
|
||||
values.sido,
|
||||
instSeCd,
|
||||
gugunSkipYn,
|
||||
]
|
||||
: null;
|
||||
const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
|
||||
sggKey,
|
||||
@@ -163,39 +227,60 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
onSuccess: (data) => {
|
||||
if (data.status !== "success") {
|
||||
alert(`시군구 목록을 불러오는데 실패했습니다.
|
||||
새로고침 후 다시 시도해주세요.`);
|
||||
새로고침 후 다시 시도해주세요.`);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
alert(`시군구 목록을 불러오는데 실패했습니다.
|
||||
새로고침 후 다시 시도해주세요.`);
|
||||
새로고침 후 다시 시도해주세요.`);
|
||||
},
|
||||
},
|
||||
);
|
||||
const sggOptions = sggList?.data ?? [];
|
||||
const isDoSelected = isDoSido(selectedSido);
|
||||
const firstConcreteSggOption = firstConcreteSgg(sggOptions);
|
||||
// 도: 빈 값일 때만 첫 시군구로 보이게. ALL은 사용자가 고른 전체 유지
|
||||
const gugunSelectValue =
|
||||
isDoSelected && !values.gugun
|
||||
? (firstConcreteSggOption?.gugun ?? undefined)
|
||||
: values.gugun || undefined;
|
||||
|
||||
// 시도 설정 후 시군구 목록이 오면 첫 번째 항목 자동 선택
|
||||
// 도: 초기/시도 변경(빈 값)만 첫 시군구. 전체(ALL)는 유지. 그 외: 전체
|
||||
useEffect(() => {
|
||||
if (!mergedProps.gugun) return;
|
||||
if (props.defaultValues?.gugun) return;
|
||||
if (props.defaultRegionAll) return;
|
||||
if (!values.sido || values.sido === "ALL") return;
|
||||
if (!selectedSido) return;
|
||||
|
||||
const firstSgg = sggList?.data?.[0];
|
||||
if (!firstSgg) return;
|
||||
if (!isDoSelected) {
|
||||
setValues((prev) => {
|
||||
if (prev.gugun) return prev;
|
||||
return {
|
||||
...prev,
|
||||
gugun: "ALL",
|
||||
bsnmCd: selectedSido.bsnmCd || prev.bsnmCd,
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!firstConcreteSggOption) return;
|
||||
|
||||
setValues((prev) => {
|
||||
if (prev.gugun) return prev;
|
||||
return {
|
||||
...prev,
|
||||
gugun: firstSgg.gugun,
|
||||
bsnmCd: firstSgg.bsnmCd,
|
||||
gugun: firstConcreteSggOption.gugun,
|
||||
bsnmCd: firstConcreteSggOption.bsnmCd,
|
||||
};
|
||||
});
|
||||
}, [
|
||||
sggList,
|
||||
values.sido,
|
||||
firstConcreteSggOption,
|
||||
isDoSelected,
|
||||
mergedProps.gugun,
|
||||
props.defaultValues?.gugun,
|
||||
props.defaultRegionAll,
|
||||
selectedSido,
|
||||
values.sido,
|
||||
]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -209,12 +294,17 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
};
|
||||
|
||||
const handleSidoChange = (value: string) => {
|
||||
const selectedBsnm =
|
||||
sidoList?.data?.sidoList?.find((v) => v.sido === value)?.bsnmCd || "";
|
||||
const selected =
|
||||
value === "ALL"
|
||||
? undefined
|
||||
: sidoList?.data?.sidoList?.find((v) => v.sido === value);
|
||||
const selectedBsnm = selected?.bsnmCd || "";
|
||||
const gugunAll =
|
||||
props.defaultRegionAll || value === "ALL" || !isDoSido(selected);
|
||||
setValues((prev) => ({
|
||||
...prev,
|
||||
sido: value,
|
||||
gugun: "",
|
||||
gugun: gugunAll ? "ALL" : "",
|
||||
bsnmCd: selectedBsnm,
|
||||
}));
|
||||
};
|
||||
@@ -224,7 +314,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
const selectedBsnm =
|
||||
value === "ALL"
|
||||
? sidoList?.data?.sidoList?.find((v) => v.sido === values.sido)
|
||||
?.bsnmCd || ""
|
||||
?.bsnmCd || ""
|
||||
: listForBsnm.find((v) => v.gugun === value)?.bsnmCd || "";
|
||||
|
||||
setValues((prev) => ({
|
||||
@@ -274,7 +364,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-label">시군구</Label>
|
||||
<Select
|
||||
value={values.gugun}
|
||||
value={gugunSelectValue}
|
||||
onValueChange={handleSggChange}
|
||||
>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
@@ -377,20 +467,27 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
<SelectValue placeholder="전체"></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='ALL'>전체</SelectItem>
|
||||
<SelectItem value='500톤이상'>500톤이상</SelectItem>
|
||||
<SelectItem value='500톤미만'>500톤미만</SelectItem>
|
||||
<SelectItem value="ALL">전체</SelectItem>
|
||||
<SelectItem value="500톤이상">
|
||||
500톤이상
|
||||
</SelectItem>
|
||||
<SelectItem value="500톤미만">
|
||||
500톤미만
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
{mergedProps.isDetail && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-label cursor-pointer" htmlFor="isDetail">
|
||||
<Label
|
||||
className="cursor-pointer text-label"
|
||||
htmlFor={isDetailInputId}
|
||||
>
|
||||
세부 데이터 포함
|
||||
</Label>
|
||||
<Checkbox
|
||||
id="isDetail"
|
||||
id={isDetailInputId}
|
||||
checked={values.isDetail}
|
||||
onCheckedChange={(value) =>
|
||||
setValues({
|
||||
@@ -403,7 +500,11 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
)}
|
||||
{mergedProps.isSummary && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-label cursor-pointer">
|
||||
<Label
|
||||
className="cursor-pointer text-label"
|
||||
title="시·군·구 및 읍·면·동 변경에 따라 인구 및 관로 등 공표된 통계수치와 일부 상이할 수 있으니 유의 바랍니다."
|
||||
>
|
||||
<i className="diveicon di-exclamation-circle mr-1" />
|
||||
요약표 포함
|
||||
</Label>
|
||||
<Select
|
||||
@@ -416,11 +517,17 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
<SelectValue placeholder="선택 안함"></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ALL">선택 안함</SelectItem>
|
||||
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => (
|
||||
<SelectItem value={i.toString()} key={i.toString()}>{`${i}개년`}</SelectItem>
|
||||
))
|
||||
}
|
||||
<SelectItem value="ALL">
|
||||
선택 안함
|
||||
</SelectItem>
|
||||
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(
|
||||
(i) => (
|
||||
<SelectItem
|
||||
value={i.toString()}
|
||||
key={i.toString()}
|
||||
>{`${i}개년`}</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -455,7 +562,9 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
</Button>
|
||||
)}
|
||||
<Button className="h-[3.2rem] w-[96px]" type="submit">
|
||||
{props.searchButtonText ? props.searchButtonText : '조회'}
|
||||
{props.searchButtonText
|
||||
? props.searchButtonText
|
||||
: "조회"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectValue,
|
||||
LoadingWithAnimation,
|
||||
} from "@common/components";
|
||||
import CompactDataGrid from "./CompactDataGrid";
|
||||
import SaveButton from "../button/SaveButton";
|
||||
import { ValueType } from "realgrid";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { isGugunRequiredSido } from "@/entities/statistics";
|
||||
import type {
|
||||
SeweragePipeFacilitiesListRequest,
|
||||
SeweragePipeFacilityAdminAreaListRequest,
|
||||
SeweragePipeFactConnectionSaveItem,
|
||||
SeweragePipeFactConnectionSaveRequest,
|
||||
} from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface";
|
||||
import type {
|
||||
ApiResponse,
|
||||
SearchCondition,
|
||||
Sgg,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { ColumnInfo } from "../type/DataGrid.type";
|
||||
import type {
|
||||
SeweragePipeFacilitiesListRequest,
|
||||
SeweragePipeFacilityAdminAreaListRequest,
|
||||
SeweragePipeFactConnectionSaveRequest,
|
||||
SeweragePipeFactConnectionSaveItem,
|
||||
} from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface";
|
||||
import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service";
|
||||
import type { SearchFormValues } from "../type/SearchForm.type";
|
||||
import { doNameList } from "../util/column/input/DoList";
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
LoadingWithAnimation,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
||||
import type { ColumnInfo } from "../type/DataGrid.type";
|
||||
import type { SearchFormValues } from "../type/SearchForm.type";
|
||||
|
||||
import CompactDataGrid from "./CompactDataGrid";
|
||||
|
||||
/** 다이얼로그 좌측: 처리시설 정보 (순번 없음) */
|
||||
const treatmentFacilityColumnInfo: ColumnInfo = {
|
||||
@@ -114,9 +115,9 @@ const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
|
||||
};
|
||||
|
||||
const getSggAllListFetcher = async (
|
||||
key: [string, string],
|
||||
key: [string, string, "Y" | "N"],
|
||||
): Promise<ApiResponse<Sgg[]>> => {
|
||||
return statisticsApi.getSggList(key[1]);
|
||||
return statisticsApi.getSggList(key[1], { gugunSkipYn: key[2] });
|
||||
};
|
||||
|
||||
type SidoSggFacilityNameSearchFormProps = {
|
||||
@@ -182,9 +183,16 @@ const SidoSggFacilityNameSearchForm = ({
|
||||
Record<string, string | number> | null
|
||||
>(null);
|
||||
const didAutoFetchRef = useRef(false);
|
||||
const isSavingConnectionRef = useRef(false);
|
||||
|
||||
const sggKey: [string, string] | null =
|
||||
sido && sido !== "ALL" ? ["sgg-all", sido] : null;
|
||||
const selectedSido = sidoList?.data?.sidoList?.find(
|
||||
(item) => item.sido === sido,
|
||||
);
|
||||
const gugunSkipYn = selectedSido?.gugunSkipYn ?? "Y";
|
||||
const sggKey: [string, string, "Y" | "N"] | null =
|
||||
sido && sido !== "ALL" && sidoList?.data
|
||||
? ["sgg-all", sido, gugunSkipYn]
|
||||
: null;
|
||||
const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
|
||||
sggKey,
|
||||
getSggAllListFetcher,
|
||||
@@ -297,7 +305,11 @@ const SidoSggFacilityNameSearchForm = ({
|
||||
swtrmtFcltCd,
|
||||
sewerBzmnCd: initialSearchValues?.bsnmCd || '',
|
||||
ctpvNm: initialSidoValue || '',
|
||||
...(doNameList.includes(initialSidoValue) ? { sggNm: initialGugunValue } : {}),
|
||||
...(isGugunRequiredSido(sidoList?.data?.sidoList, {
|
||||
sido: initialSidoValue,
|
||||
})
|
||||
? { sggNm: initialGugunValue }
|
||||
: {}),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -329,119 +341,213 @@ const SidoSggFacilityNameSearchForm = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveAdminRegions = async () => {
|
||||
const canEditFactConnection = async (): Promise<boolean> => {
|
||||
const statusCodeResponse = await statisticsApi.CheckStatusCode({
|
||||
sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "",
|
||||
statsCd: 'ST00020002',
|
||||
statsYr: statsYr?.trim() ?? ''
|
||||
statsCd: "ST00020002",
|
||||
statsYr: statsYr?.trim() ?? "",
|
||||
});
|
||||
|
||||
if (statusCodeResponse.status === 'success') {
|
||||
if (!['0', '1', '4'].includes(statusCodeResponse.data)) {
|
||||
const codeName = statusCodeResponse.data === '2' ? '제출' : (statusCodeResponse.data === '3' ? '승인' : '검증중');
|
||||
alert(`시설별 설치 현황 데이터가 ${codeName} 상태일 때는 처리 시설 정보를 변경할 수 없습니다.`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
alert('오류가 발생했습니다.\n관리자에게 문의하세요')
|
||||
if (statusCodeResponse.status === "success") {
|
||||
if (!["0", "1", "4"].includes(statusCodeResponse.data)) {
|
||||
const codeName =
|
||||
statusCodeResponse.data === "2"
|
||||
? "제출"
|
||||
: statusCodeResponse.data === "3"
|
||||
? "승인"
|
||||
: "검증중";
|
||||
alert(
|
||||
`시설별 설치 현황 데이터가 ${codeName} 상태일 때는 처리 시설 정보를 변경할 수 없습니다.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
alert("오류가 발생했습니다.\n관리자에게 문의하세요");
|
||||
return false;
|
||||
};
|
||||
|
||||
const persistFactConnection = async (
|
||||
saveList: SeweragePipeFactConnectionSaveItem[],
|
||||
deleteList: string[],
|
||||
): Promise<boolean> => {
|
||||
if (!statsYr?.trim()) {
|
||||
alert("상단에서 통계연도로 조회한 뒤 이용해 주세요.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!selectedFacilityRow) {
|
||||
alert("처리시설을 먼저 선택해주세요.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (saveList.length === 0 && deleteList.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (!(await canEditFactConnection())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasChanged = adminRegionRows.some(
|
||||
(row) =>
|
||||
(row.__checked === "1" && row.isConnection !== "1") ||
|
||||
(row.__checked !== "1" && row.isConnection === "1"),
|
||||
);
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const request: SeweragePipeFactConnectionSaveRequest = {
|
||||
ctpvNm: String(selectedFacilityRow.ctpvNm ?? ""),
|
||||
sggNm: String(selectedFacilityRow.sggNm ?? ""),
|
||||
fcltSn: String(selectedFacilityRow.fcltSn ?? ""),
|
||||
statsYr: statsYr.trim(),
|
||||
swtrmtFcltCd: String(selectedFacilityRow.fcltCd ?? ""),
|
||||
swtrmtFcltNm: String(selectedFacilityRow.fcltNm ?? ""),
|
||||
prcsFcltCpct: Number(selectedFacilityRow.fcltCpct ?? 0),
|
||||
sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "",
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList,
|
||||
deleteList,
|
||||
};
|
||||
|
||||
const saveList: SeweragePipeFactConnectionSaveItem[] = adminRegionRows
|
||||
.filter((row) => row.__checked === "1")
|
||||
.map((row) => ({
|
||||
admdstCd: String(row.admdstCd ?? ""),
|
||||
admdstNm: String(row.emdNm ?? ""),
|
||||
}))
|
||||
.filter((row) => row.admdstCd && row.admdstNm);
|
||||
|
||||
const deleteList = adminRegionRows
|
||||
.filter((row) => row.__checked !== "1" && row.isConnection === "1")
|
||||
.map((row) => String(row.admdstCd ?? ""))
|
||||
.filter(Boolean);
|
||||
|
||||
if (!hasChanged) {
|
||||
alert("변경된 연결 정보가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const request: SeweragePipeFactConnectionSaveRequest = {
|
||||
ctpvNm: String(selectedFacilityRow.ctpvNm ?? ""),
|
||||
sggNm: String(selectedFacilityRow.sggNm ?? ""),
|
||||
fcltSn: String(selectedFacilityRow.fcltSn ?? ""),
|
||||
statsYr: statsYr.trim(),
|
||||
swtrmtFcltCd: String(selectedFacilityRow.fcltCd ?? ""),
|
||||
swtrmtFcltNm: String(selectedFacilityRow.fcltNm ?? ""),
|
||||
prcsFcltCpct: Number(selectedFacilityRow.fcltCpct ?? 0),
|
||||
sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
saveList,
|
||||
deleteList,
|
||||
};
|
||||
|
||||
const response =
|
||||
await statisticsInputSewPipeApiService.saveFactConnection(
|
||||
request,
|
||||
);
|
||||
|
||||
if (response.status !== "success") {
|
||||
alert(response.message ?? "연결관계 저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setAdminRegionRows((prev) =>
|
||||
prev.map((row) => ({
|
||||
...row,
|
||||
isConnection: row.__checked === "1" ? "1" : "0",
|
||||
})),
|
||||
const response =
|
||||
await statisticsInputSewPipeApiService.saveFactConnection(
|
||||
request,
|
||||
);
|
||||
await onSaveSuccess?.();
|
||||
alert("저장되었습니다.");
|
||||
} catch {
|
||||
alert("연결관계 저장 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
void run();
|
||||
if (response.status !== "success") {
|
||||
alert(response.message ?? "연결관계 저장에 실패했습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
await onSaveSuccess?.();
|
||||
return true;
|
||||
} catch {
|
||||
alert("연결관계 저장 중 오류가 발생했습니다.");
|
||||
return false;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdminRegionItemChecked = (
|
||||
const syncConnectionFlags = (
|
||||
rows: Record<string, string | number>[],
|
||||
): Record<string, string | number>[] =>
|
||||
rows.map((row) => ({
|
||||
...row,
|
||||
isConnection: row.__checked === "1" ? "1" : "0",
|
||||
}));
|
||||
|
||||
const revertCheckedToConnection = (
|
||||
rows: Record<string, string | number>[],
|
||||
): Record<string, string | number>[] =>
|
||||
rows.map((row) => ({
|
||||
...row,
|
||||
__checked: row.isConnection === "1" ? "1" : "0",
|
||||
}));
|
||||
|
||||
const handleAdminRegionItemChecked = async (
|
||||
itemIndex: number,
|
||||
checked: boolean,
|
||||
) => {
|
||||
if (isSavingConnectionRef.current) {
|
||||
// RealGrid가 이미 토글했을 수 있어 __checked 기준으로 재동기화
|
||||
setAdminRegionRows((prev) => prev.map((row) => ({ ...row })));
|
||||
return;
|
||||
}
|
||||
|
||||
const target = adminRegionRows[itemIndex];
|
||||
if (!target) return;
|
||||
|
||||
const nextChecked = checked ? "1" : "0";
|
||||
const prevChecked = target.isConnection === "1" ? "1" : "0";
|
||||
|
||||
setAdminRegionRows((prev) =>
|
||||
prev.map((row, idx) =>
|
||||
idx === itemIndex
|
||||
? { ...row, __checked: checked ? "1" : "0" }
|
||||
? { ...row, __checked: nextChecked }
|
||||
: row,
|
||||
),
|
||||
);
|
||||
|
||||
// 서버 상태와 동일하면 API 호출 없이 UI만 반영
|
||||
if (nextChecked === prevChecked) {
|
||||
return;
|
||||
}
|
||||
|
||||
const saveList: SeweragePipeFactConnectionSaveItem[] =
|
||||
checked
|
||||
? [
|
||||
{
|
||||
admdstCd: String(target.admdstCd ?? ""),
|
||||
admdstNm: String(target.emdNm ?? ""),
|
||||
},
|
||||
].filter((row) => row.admdstCd && row.admdstNm)
|
||||
: [];
|
||||
const deleteList =
|
||||
!checked && String(target.admdstCd ?? "")
|
||||
? [String(target.admdstCd)]
|
||||
: [];
|
||||
|
||||
if (saveList.length === 0 && deleteList.length === 0) {
|
||||
setAdminRegionRows((prev) => revertCheckedToConnection(prev));
|
||||
alert("행정구역 코드가 없어 저장할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
isSavingConnectionRef.current = true;
|
||||
const ok = await persistFactConnection(saveList, deleteList);
|
||||
isSavingConnectionRef.current = false;
|
||||
|
||||
if (!ok) {
|
||||
setAdminRegionRows((prev) => revertCheckedToConnection(prev));
|
||||
return;
|
||||
}
|
||||
|
||||
setAdminRegionRows((prev) => syncConnectionFlags(prev));
|
||||
};
|
||||
|
||||
const handleAdminRegionItemAllChecked = (checked: boolean) => {
|
||||
const handleAdminRegionItemAllChecked = async (checked: boolean) => {
|
||||
if (isSavingConnectionRef.current) {
|
||||
setAdminRegionRows((prev) => prev.map((row) => ({ ...row })));
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = adminRegionRows;
|
||||
if (snapshot.length === 0) return;
|
||||
|
||||
setAdminRegionRows((prev) =>
|
||||
prev.map((row) => ({ ...row, __checked: checked ? "1" : "0" })),
|
||||
prev.map((row) => ({
|
||||
...row,
|
||||
__checked: checked ? "1" : "0",
|
||||
})),
|
||||
);
|
||||
|
||||
const saveList: SeweragePipeFactConnectionSaveItem[] = checked
|
||||
? snapshot
|
||||
.filter((row) => row.isConnection !== "1")
|
||||
.map((row) => ({
|
||||
admdstCd: String(row.admdstCd ?? ""),
|
||||
admdstNm: String(row.emdNm ?? ""),
|
||||
}))
|
||||
.filter((row) => row.admdstCd && row.admdstNm)
|
||||
: [];
|
||||
const deleteList = !checked
|
||||
? snapshot
|
||||
.filter((row) => row.isConnection === "1")
|
||||
.map((row) => String(row.admdstCd ?? ""))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
if (saveList.length === 0 && deleteList.length === 0) {
|
||||
setAdminRegionRows((prev) => syncConnectionFlags(prev));
|
||||
return;
|
||||
}
|
||||
|
||||
isSavingConnectionRef.current = true;
|
||||
const ok = await persistFactConnection(saveList, deleteList);
|
||||
isSavingConnectionRef.current = false;
|
||||
|
||||
if (!ok) {
|
||||
setAdminRegionRows((prev) => revertCheckedToConnection(prev));
|
||||
return;
|
||||
}
|
||||
|
||||
setAdminRegionRows((prev) => syncConnectionFlags(prev));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -575,8 +681,10 @@ const SidoSggFacilityNameSearchForm = ({
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<SaveButton saveFunc={handleSaveAdminRegions} />
|
||||
<div>
|
||||
<span className="text-lg text-gray-500 float-end">
|
||||
* 체크 박스를 선택 / 해제 시 반영됩니다.
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
{isLoading && (
|
||||
|
||||
@@ -16,6 +16,7 @@ const SplitDataGrid = ({
|
||||
leftCellStyleCallback,
|
||||
rightOnItemChecked,
|
||||
rightOnItemAllChecked,
|
||||
rightCheckableCallback,
|
||||
enableSort = false,
|
||||
}: SplitDataGridProps) => {
|
||||
return (
|
||||
@@ -39,6 +40,7 @@ const SplitDataGrid = ({
|
||||
setRowData={setRightRowData}
|
||||
onItemChecked={rightOnItemChecked}
|
||||
onItemAllChecked={rightOnItemAllChecked}
|
||||
checkableCallback={rightCheckableCallback}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,32 +18,48 @@ function mergeCellStyleCallbackResult(
|
||||
baseStyleName: string | undefined,
|
||||
extra: CellStyleCallbackResult | undefined,
|
||||
): CellStyleCallbackResult {
|
||||
let styleName: string | undefined;
|
||||
let extraObj: Extract<CellStyleCallbackResult, object> | undefined;
|
||||
|
||||
if (!extra) {
|
||||
return baseStyleName ? { styleName: baseStyleName } : {};
|
||||
styleName = baseStyleName || undefined;
|
||||
} else if (typeof extra === "string") {
|
||||
styleName = [extra, baseStyleName].filter(Boolean).join(" ") || undefined;
|
||||
} else {
|
||||
extraObj = extra;
|
||||
styleName =
|
||||
[extra.styleName, baseStyleName].filter(Boolean).join(" ") ||
|
||||
undefined;
|
||||
}
|
||||
if (typeof extra === "string") {
|
||||
const styleName = [extra, baseStyleName].filter(Boolean).join(" ");
|
||||
return styleName ? { styleName } : {};
|
||||
}
|
||||
const styleName = [extra.styleName, baseStyleName].filter(Boolean).join(" ");
|
||||
return {
|
||||
...extra,
|
||||
|
||||
if (!styleName && !extraObj) return {};
|
||||
|
||||
const result: CellStyleCallbackResult = {
|
||||
...extraObj,
|
||||
...(styleName ? { styleName } : {}),
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
import SplitDataGrid from "./SplitDataGrid";
|
||||
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
|
||||
import { wrapWithFormatTruncate } from "@/components/util/grid/decimalNumber"
|
||||
import {
|
||||
buildExcelExportFileBaseName,
|
||||
buildFixedColumnNameSet,
|
||||
getExcelImportColumnFieldNames,
|
||||
getNonEditableColumnFieldNames,
|
||||
hasAdmdstCdColumn,
|
||||
isExcelImportAdmdstCdInvalid,
|
||||
mergeExcelImportWithExistingRows,
|
||||
prepareGridColumnsForExcelExport,
|
||||
releaseFixedOptionsForExcelExport,
|
||||
resolveColumnStyleName,
|
||||
restoreFixedOptionsAfterExcelExport,
|
||||
restoreGridColumnsAfterExcelExport,
|
||||
shouldPrepareColumnsForExcelExport,
|
||||
disableAlternateRowStyle,
|
||||
} from "@/components/util/gridExcelExport"
|
||||
import { useTabStore } from "@/store/tab-store"
|
||||
|
||||
@@ -88,7 +104,7 @@ function safeGridViewRefresh(gv: { refresh?: () => void } | null | undefined) {
|
||||
try {
|
||||
gv.refresh();
|
||||
} catch {
|
||||
/* noop */
|
||||
console.error('refresh error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +253,7 @@ function sortRowsGroupPreserving(
|
||||
}
|
||||
|
||||
/** columnInfo → RealGrid DataField 배열 (computed → valueCallback 포함, setFields용) */
|
||||
function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueType; computed?: import("../type/DataGrid.type").ComputedFieldConfig }[]) {
|
||||
function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueType; dataFormat?: string; computed?: import("../type/DataGrid.type").ComputedFieldConfig }[]) {
|
||||
return columns.map((col) => {
|
||||
const field: { fieldName: string; dataType: string; valueCallback?: (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number } = {
|
||||
fieldName: col.fieldName,
|
||||
@@ -253,7 +269,7 @@ function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueT
|
||||
const hasSumOfMaxGroups = "groups" in col.computed && Array.isArray(col.computed.groups) && col.computed.groups.length > 0;
|
||||
const hasSumPerTenThousand = col.computed.type === "sumPerTenThousand" && "numeratorFields" in col.computed && col.computed.numeratorFields.length > 0 && "denominatorField" in col.computed && Boolean(col.computed.denominatorField);
|
||||
if (hasFields || hasNumeratorDenominator || hasMaxOfFieldAndSum || hasDifferenceOfSums || hasHalfSumOfSums || hasSumOfTwoHalfSumOfSums || hasSumOfMaxGroups || hasSumPerTenThousand) {
|
||||
field.valueCallback = getComputedValueCallback(col.computed) as (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number;
|
||||
field.valueCallback = wrapWithFormatTruncate(getComputedValueCallback(col.computed), col.dataFormat);
|
||||
}
|
||||
}
|
||||
return field;
|
||||
@@ -282,7 +298,7 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string
|
||||
const TabDataGrid = ({
|
||||
columnInfo,
|
||||
isExcelImport = true,
|
||||
excelImportSkipNonEditable = true,
|
||||
excelImportSkipNonEditable = false,
|
||||
excelImportSewerBzmnCd,
|
||||
excelExportSearchContext,
|
||||
isExcelExport = true,
|
||||
@@ -313,17 +329,23 @@ const TabDataGrid = ({
|
||||
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(undefined);
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
|
||||
const attachCellStyleCallbacksForTab = useCallback((tabKey: string, columns: ColumnConfig[], attempt = 0) => {
|
||||
if (!cellStyleCallbackRef.current) return;
|
||||
const attachCellStyleCallbacksForTab = useCallback((tabKey: string, columns: ColumnConfig[], columnLayout: ColumnLayoutItem[] | undefined, fixedCols: number | undefined, attempt = 0) => {
|
||||
const el = gridRefs.current.get(tabKey);
|
||||
const gv = el?.gridView;
|
||||
if (!gv) return;
|
||||
|
||||
const fixedColumnNameSet = buildFixedColumnNameSet(columns, columnLayout, fixedCols);
|
||||
const hasUserCellStyleCallback = Boolean(cellStyleCallbackRef.current);
|
||||
if (fixedColumnNameSet.size === 0 && !hasUserCellStyleCallback) return;
|
||||
|
||||
let attachedCount = 0;
|
||||
for (const column of columns) {
|
||||
const isFixedColumn = fixedColumnNameSet.has(column.name);
|
||||
if (!isFixedColumn && !hasUserCellStyleCallback) continue;
|
||||
|
||||
const gridCol = safeColumnByName(gv, column.name);
|
||||
if (!gridCol) continue;
|
||||
const baseStyleName = column.styleName;
|
||||
const baseStyleName = resolveColumnStyleName(column, fixedColumnNameSet);
|
||||
gridCol.styleCallback = (grid, dataCell) => {
|
||||
const dc = dataCell as {
|
||||
itemIndex?: number;
|
||||
@@ -338,19 +360,21 @@ const TabDataGrid = ({
|
||||
return mergeCellStyleCallbackResult(baseStyleName, undefined);
|
||||
}
|
||||
const row = data[itemIndex] as Record<string, string | number>;
|
||||
const extra = cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
});
|
||||
const extra = hasUserCellStyleCallback
|
||||
? cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
})
|
||||
: undefined;
|
||||
return mergeCellStyleCallbackResult(baseStyleName, extra);
|
||||
};
|
||||
attachedCount += 1;
|
||||
}
|
||||
|
||||
if (attachedCount === 0 && attempt < 20) {
|
||||
setTimeout(() => attachCellStyleCallbacksForTab(tabKey, columns, attempt + 1), 100);
|
||||
setTimeout(() => attachCellStyleCallbacksForTab(tabKey, columns, columnLayout, fixedCols, attempt + 1), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -459,6 +483,14 @@ const TabDataGrid = ({
|
||||
const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep
|
||||
? prepareGridColumnsForExcelExport(gridView, columns)
|
||||
: undefined;
|
||||
const savedFixedColCount = columnInfoItem.fixedColumns
|
||||
? releaseFixedOptionsForExcelExport(
|
||||
gridView,
|
||||
columnInfoItem.fixedColumns,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
disableAlternateRowStyle(gridView, true);
|
||||
|
||||
const exportBaseName = buildExcelExportFileBaseName(
|
||||
menuName,
|
||||
@@ -473,6 +505,12 @@ const TabDataGrid = ({
|
||||
footer: 'hidden',
|
||||
allColumns: !useExcelColumnVisibilityPrep,
|
||||
done: () => {
|
||||
if (savedFixedColCount !== undefined) {
|
||||
restoreFixedOptionsAfterExcelExport(
|
||||
gridView,
|
||||
savedFixedColCount,
|
||||
);
|
||||
}
|
||||
if (excelExportVisibilitySnapshot) {
|
||||
restoreGridColumnsAfterExcelExport(gridView, excelExportVisibilitySnapshot);
|
||||
}
|
||||
@@ -968,6 +1006,11 @@ const TabDataGrid = ({
|
||||
return null;
|
||||
}
|
||||
const { columns, columnLayout } = info;
|
||||
const fixedColumnNameSet = buildFixedColumnNameSet(
|
||||
columns,
|
||||
columnLayout,
|
||||
info.fixedColumns,
|
||||
);
|
||||
const currentRowData = rowDataMap.get(tabKey) || [];
|
||||
|
||||
return (
|
||||
@@ -1074,6 +1117,7 @@ const TabDataGrid = ({
|
||||
gridView.setOptions({
|
||||
summaryMode: "aggregate"
|
||||
});
|
||||
disableAlternateRowStyle(gridView);
|
||||
|
||||
// footerUserSpans가 있는 경우 setColumnLayout으로 설정
|
||||
const checkFooterUserSpans = (layout: unknown[]): boolean => {
|
||||
@@ -1189,9 +1233,27 @@ const TabDataGrid = ({
|
||||
if (currentInfo.fixedColumns !== undefined && currentInfo.fixedColumns > 0) {
|
||||
gridView.setFixedOptions({
|
||||
colCount: currentInfo.fixedColumns,
|
||||
resizable: true,
|
||||
});
|
||||
}
|
||||
|
||||
const tabFixedSet = buildFixedColumnNameSet(
|
||||
columns,
|
||||
currentInfo.columnLayout,
|
||||
currentInfo.fixedColumns,
|
||||
);
|
||||
columns.forEach((col) => {
|
||||
const styleName = resolveColumnStyleName(
|
||||
col,
|
||||
tabFixedSet,
|
||||
);
|
||||
gridView.setColumnProperty?.(
|
||||
col.name,
|
||||
"styleName",
|
||||
styleName,
|
||||
);
|
||||
});
|
||||
|
||||
// 각 탭별 ref 초기화
|
||||
if (!sortDirRefs.current.has(tabKey)) {
|
||||
sortDirRefs.current.set(tabKey, { current: {} });
|
||||
@@ -1247,9 +1309,17 @@ const TabDataGrid = ({
|
||||
const onSortingHandler = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef, findGridElement);
|
||||
(gridView as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = enableSort ? onSortingHandler : () => false;
|
||||
|
||||
if (cellStyleCallbackRef.current) {
|
||||
if (
|
||||
cellStyleCallbackRef.current ||
|
||||
tabFixedSet.size > 0
|
||||
) {
|
||||
setTimeout(() => {
|
||||
attachCellStyleCallbacksForTab(tabKey, columns);
|
||||
attachCellStyleCallbacksForTab(
|
||||
tabKey,
|
||||
columns,
|
||||
currentInfo.columnLayout,
|
||||
currentInfo.fixedColumns,
|
||||
);
|
||||
}, 100);
|
||||
}
|
||||
}, 150);
|
||||
@@ -1262,7 +1332,16 @@ const TabDataGrid = ({
|
||||
layout={columnLayout}
|
||||
rows={currentRowData}
|
||||
>
|
||||
{columns.map((column) => (
|
||||
{columns.map((column) => {
|
||||
const resolvedStyleName =
|
||||
resolveColumnStyleName(
|
||||
column,
|
||||
fixedColumnNameSet,
|
||||
) ||
|
||||
(column.editable === false
|
||||
? "rg-cell-readonly"
|
||||
: undefined);
|
||||
return (
|
||||
<RGDataColumn
|
||||
key={column.name}
|
||||
name={column.name}
|
||||
@@ -1271,11 +1350,9 @@ const TabDataGrid = ({
|
||||
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.00' })}
|
||||
header={column.header}
|
||||
editable={column.editable}
|
||||
{...(column.styleName != null
|
||||
? { styleName: column.styleName }
|
||||
: column.editable === false
|
||||
? { styleName: 'rg-cell-readonly' as const }
|
||||
: {})}
|
||||
{...(resolvedStyleName != null
|
||||
? { styleName: resolvedStyleName }
|
||||
: {})}
|
||||
{...(column.visible === false && { visible: false })}
|
||||
footer={column.footer && !Array.isArray(column.footer) ? {
|
||||
text: (column.footer as { text?: string }).text,
|
||||
@@ -1285,7 +1362,8 @@ const TabDataGrid = ({
|
||||
mergeRule={column.mergeCells ? { criteria: column.mergeGroupField ? `values['${column.mergeGroupField}'] + value` : 'value' } : undefined}
|
||||
sortable={enableSort && !column.sortdisable}
|
||||
/>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</RealGridReact>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,12 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -21,17 +26,17 @@ import {
|
||||
DialogTitle,
|
||||
LoadingWithAnimation,
|
||||
} from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { cellContentTooltip } from "@/components/module/verificationResultCellTooltip";
|
||||
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
||||
import type {
|
||||
CellEditedCallbackParams,
|
||||
CellStyleCallbackParams,
|
||||
ColumnInfo,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
type VerificationResultGridRow = Record<string, string | number>;
|
||||
|
||||
@@ -41,8 +46,8 @@ const VERIFICATION_METHOD_CODES: VerificationMethodCd[] = ["NV", "IMR", "HB"];
|
||||
|
||||
const VRFC_MTHD_NAME_MAP: Record<VerificationMethodCd, string> = {
|
||||
NV: "일반",
|
||||
IMR: "I-MR",
|
||||
HB: "HB",
|
||||
IMR: "시계열",
|
||||
HB: "모집단",
|
||||
};
|
||||
|
||||
export type VerificationExceptionSubmitItem = {
|
||||
@@ -63,7 +68,7 @@ function setRowCheckByItemIndex(
|
||||
try {
|
||||
gv?.checkItem?.(itemIndex, checked, false);
|
||||
} catch {
|
||||
/* RealGrid 체크 API 미지원 등 */
|
||||
console.error('RealGrid 체크 API error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,11 +98,12 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
{ name: "vrfcMthdCd", fieldName: "vrfcMthdCd", header: { text: "검증유형코드" }, editable: false, visible: false },
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
||||
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false },
|
||||
{ name: "fcltNm", fieldName: "fcltNm", 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: "nowCn", fieldName: "nowCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "cmprCn", fieldName: "cmprCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "vrfcSttsNm", fieldName: "vrfcSttsNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
|
||||
{
|
||||
@@ -111,11 +117,12 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
"vrfcMthdNm",
|
||||
"ctpvNm",
|
||||
"admdstNm",
|
||||
"fcltNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYn",
|
||||
"nowCn",
|
||||
"cmprCn",
|
||||
"vrfcSttsNm",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"prcsOpnnExpln",
|
||||
@@ -125,7 +132,7 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
export interface VerificationResultDialogQueryContext {
|
||||
sewerBzmnCd: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
sggNm?: string;
|
||||
statsYr: string;
|
||||
statsCd: string;
|
||||
}
|
||||
@@ -201,11 +208,12 @@ export function VerificationResultDialog({
|
||||
vrfcMthdNm: VRFC_MTHD_NAME_MAP[vrfcMthdCd] ?? item.vrfcMthdNm,
|
||||
ctpvNm: item.ctpvNm,
|
||||
admdstNm: item.admdstNm ?? item.sggNm ?? "-",
|
||||
fcltNm: item.fcltNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYn: item.prcsYnNm ?? PRCS_YN_LABEL[item.prcsYn] ?? item.prcsYn,
|
||||
nowCn: item.nowCn ?? "-",
|
||||
cmprCn: item.cmprCn ?? "-",
|
||||
vrfcSttsNm: item.vrfcSttsNm ?? PRCS_YN_LABEL[item.vrfcSttsCd] ?? item.vrfcSttsCd,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "",
|
||||
@@ -343,7 +351,7 @@ export function VerificationResultDialog({
|
||||
sewerBzmnCd: stableQueryContext.sewerBzmnCd,
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
exceptionList: payload,
|
||||
});
|
||||
if (response.status === "success") {
|
||||
@@ -413,6 +421,7 @@ export function VerificationResultDialog({
|
||||
getGridDataRef={getGridDataRef}
|
||||
onCellEdited={onPrcsOpnnExplnEdited}
|
||||
cellStyleCallback={verificationMthdCellStyle}
|
||||
cellTooltipCallback={cellContentTooltip}
|
||||
onItemChecked={(itemIndex, checked) => {
|
||||
setCheckedIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
|
||||
@@ -10,9 +10,10 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
@@ -30,18 +31,20 @@ import {
|
||||
TabsTrigger,
|
||||
} from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { cellContentTooltip } from "@/components/module/verificationResultCellTooltip";
|
||||
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
||||
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
type VerificationResultGridRow = Record<string, string | number>;
|
||||
type VerificationHistoryTabKey = "NV" | "IMR" | "HB";
|
||||
|
||||
const verificationHistoryTabs: { value: VerificationHistoryTabKey; label: string }[] = [
|
||||
{ value: "NV", label: "일반검증 내역" },
|
||||
{ value: "IMR", label: "I-MR 검증내역" },
|
||||
{ value: "HB", label: "HB 검증내역" },
|
||||
{ value: "IMR", label: "시계열 검증내역" },
|
||||
{ value: "HB", label: "모집단 검증내역" },
|
||||
];
|
||||
|
||||
const PAGE_SIZE = 30;
|
||||
@@ -88,7 +91,7 @@ function uncheckGridRow(gridRef: MutableRefObject<RealGridReact | null>, itemInd
|
||||
try {
|
||||
(gridRef.current?.gridView as GridViewWithCheckApi | undefined)?.checkItem?.(itemIndex, false, false);
|
||||
} catch {
|
||||
/* ignore */
|
||||
console.error('RealGrid 체크 API error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +113,7 @@ function syncCheckedMapFromGrid(
|
||||
if (!entry) continue;
|
||||
const sn = Number(entry.vrfcSn);
|
||||
if (!Number.isFinite(sn)) continue;
|
||||
next.set(sn, String(entry.prcsYn ?? ""));
|
||||
next.set(sn, String(entry.vrfcSttsCd ?? ""));
|
||||
}
|
||||
return next;
|
||||
}
|
||||
@@ -121,15 +124,15 @@ function selectionHasNonExceptionResetAllowed(
|
||||
): boolean {
|
||||
for (const [vrfcSn, storedPrcsYn] of map) {
|
||||
const onPage = currentEntries.find((e) => Number(e.vrfcSn) === vrfcSn);
|
||||
const prcsYn = onPage ? onPage.prcsYn : storedPrcsYn;
|
||||
const prcsYn = onPage ? onPage.vrfcSttsCd : storedPrcsYn;
|
||||
if (!canExceptionReset(prcsYn)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function verificationPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined {
|
||||
if (fieldName !== "prcsYnNm") return undefined;
|
||||
const code = prcsYnCodeToNumber(row.prcsYn);
|
||||
if (fieldName !== "vrfcSttsNm") return undefined;
|
||||
const code = prcsYnCodeToNumber(row.vrfcSttsCd);
|
||||
if (code === 0 || code === 3) return "rg-cell-vrf-prcs-bad";
|
||||
return "rg-cell-vrf-prcs-ok";
|
||||
}
|
||||
@@ -138,33 +141,35 @@ const verificationResultColumnInfo: ColumnInfo = {
|
||||
columns: [
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
||||
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false },
|
||||
{ name: "fcltNm", fieldName: "fcltNm", 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: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "stVrfRjtComNm", fieldName: "stVrfRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "nowCn", fieldName: "nowCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "cmprCn", fieldName: "cmprCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "vrfcSttsNm", fieldName: "vrfcSttsNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "statsVrfcRjtComNm", fieldName: "statsVrfcRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false },
|
||||
{ name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
|
||||
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
{ name: "vrfcSttsCd", fieldName: "vrfcSttsCd", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
{ name: "vrfcSn", fieldName: "vrfcSn", header: { text: "검증일련번호" }, editable: false, visible: false },
|
||||
],
|
||||
columnLayout: [
|
||||
"ctpvNm",
|
||||
"admdstNm",
|
||||
"fcltNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYnNm",
|
||||
"stVrfRjtComNm",
|
||||
"nowCn",
|
||||
"cmprCn",
|
||||
"vrfcSttsNm",
|
||||
"statsVrfcRjtComNm",
|
||||
"prcsOpnnExpln",
|
||||
"rjctOpnnCn",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"prcsYn",
|
||||
"vrfcSttsCd",
|
||||
"vrfcSn",
|
||||
],
|
||||
};
|
||||
@@ -175,7 +180,7 @@ export interface VerificationResultHistoryDialogProps {
|
||||
queryContext?: {
|
||||
sewerBzmnCd: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
sggNm?: string;
|
||||
statsYr: string;
|
||||
statsCd: string;
|
||||
} | null;
|
||||
@@ -375,17 +380,18 @@ export function VerificationResultHistoryDialog({
|
||||
return {
|
||||
vrfcSn: item.vrfcSn,
|
||||
__checked: isChecked ? 1 : 0,
|
||||
ctpvNm: item.ctpvNm,
|
||||
ctpvNm: item.ctpvNm ?? "-",
|
||||
admdstNm: item.admdstNm ?? item.sggNm ?? "-",
|
||||
fcltNm: item.fcltNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYnNm: item.prcsYnNm ?? "-",
|
||||
stVrfRjtComNm: item.stVrfRjtComNm ?? "-",
|
||||
nowCn: item.nowCn ?? "-",
|
||||
cmprCn: item.cmprCn ?? "-",
|
||||
vrfcSttsNm: item.vrfcSttsNm ?? "-",
|
||||
statsVrfcRjtComNm: item.statsVrfcRjtComNm ?? "-",
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "-",
|
||||
rjctOpnnCn: item.rjctOpnnCn ?? "-",
|
||||
prcsYn: item.prcsYn ?? "",
|
||||
vrfcSttsCd: item.vrfcSttsCd ?? "",
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
};
|
||||
@@ -428,10 +434,10 @@ export function VerificationResultHistoryDialog({
|
||||
}
|
||||
setCheckedVrfcByTab((prev) => ({ ...prev, [tab]: syncedMap }));
|
||||
const vrfcSnList = Array.from(syncedMap.keys());
|
||||
let mdfrId: string;
|
||||
let mdfrUuid: string;
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
mdfrId = userInfo.userUnqId;
|
||||
mdfrUuid = userInfo.userUnqId;
|
||||
} catch {
|
||||
alert("사용자 정보를 불러오지 못했습니다.");
|
||||
return;
|
||||
@@ -443,7 +449,7 @@ export function VerificationResultHistoryDialog({
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
sewerBzmnCd: stableQueryContext.sewerBzmnCd,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
mdfrId,
|
||||
mdfrUuid,
|
||||
vrfcSnList,
|
||||
});
|
||||
if (response.status !== "success") {
|
||||
@@ -500,18 +506,19 @@ export function VerificationResultHistoryDialog({
|
||||
enableSort={false}
|
||||
getGridRef={gridRef}
|
||||
cellStyleCallback={verificationPrcsYnNmCellStyle}
|
||||
cellTooltipCallback={cellContentTooltip}
|
||||
onItemChecked={(itemIndex, checked) => {
|
||||
const entry = entries[itemIndex];
|
||||
if (!entry) return;
|
||||
const vrfcSn = Number(entry.vrfcSn);
|
||||
if (!Number.isFinite(vrfcSn)) return;
|
||||
if (checked && !canExceptionReset(entry.prcsYn)) {
|
||||
if (checked && !canExceptionReset(entry.vrfcSttsCd)) {
|
||||
alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE);
|
||||
uncheckGridRow(gridRef, itemIndex);
|
||||
return;
|
||||
}
|
||||
updateCheckedVrfcForTab(tab, (map) => {
|
||||
if (checked) map.set(vrfcSn, String(entry.prcsYn ?? ""));
|
||||
if (checked) map.set(vrfcSn, String(entry.vrfcSttsCd ?? ""));
|
||||
else map.delete(vrfcSn);
|
||||
});
|
||||
}}
|
||||
@@ -524,14 +531,14 @@ export function VerificationResultHistoryDialog({
|
||||
map.delete(vrfcSn);
|
||||
return;
|
||||
}
|
||||
if (canExceptionReset(entry.prcsYn)) {
|
||||
map.set(vrfcSn, String(entry.prcsYn ?? ""));
|
||||
if (canExceptionReset(entry.vrfcSttsCd)) {
|
||||
map.set(vrfcSn, String(entry.vrfcSttsCd ?? ""));
|
||||
} else {
|
||||
uncheckGridRow(gridRef, itemIndex);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (checked && entries.some((e) => !canExceptionReset(e.prcsYn))) {
|
||||
if (checked && entries.some((e) => !canExceptionReset(e.vrfcSttsCd))) {
|
||||
alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE);
|
||||
}
|
||||
}}
|
||||
@@ -558,7 +565,7 @@ export function VerificationResultHistoryDialog({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[80%]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{`${title} - ${queryContext?.statsYr} ${queryContext?.ctpvNm} ${queryContext?.sggNm}`}</DialogTitle>
|
||||
<DialogTitle>{`${title} - ${queryContext?.statsYr} ${queryContext?.ctpvNm} ${queryContext?.sggNm ? queryContext?.sggNm : ''}`}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="max-h-[85vh] space-y-5 overflow-auto">
|
||||
{errorMessage && (
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DoubleArrowLeftIcon,
|
||||
DoubleArrowRightIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
|
||||
import { Button, cn } from "@common/components";
|
||||
|
||||
export type VerificationResultHistoryPaginationProps = {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { CellTooltipCallbackParams } from "@/components/type/DataGrid.type";
|
||||
|
||||
/** RealGrid 셀 호버 시 title처럼 전체 내용 표시 */
|
||||
export function cellContentTooltip({
|
||||
row,
|
||||
fieldName,
|
||||
}: CellTooltipCallbackParams): string | undefined {
|
||||
const value = row[fieldName];
|
||||
if (value == null || value === "") return undefined;
|
||||
return String(value);
|
||||
}
|
||||
@@ -1,47 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { useTabStore as useGridTabStore } from "@rsone/components";
|
||||
import {
|
||||
type InfiniteData,
|
||||
keepPreviousData,
|
||||
useInfiniteQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { useTabStore as useGridTabStore } from "@rsone/components";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { useAlertStore, useMenuStore } from "@/shared/lib";
|
||||
import {
|
||||
findMenuItemByUrlPath,
|
||||
resolveInputPageNavigationFromStatsCd,
|
||||
} from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { CodeSelectWithAll } from "@/entities/code";
|
||||
import { verificationInputReportApi } from "@/entities/statistics/api/verification-input-report-service";
|
||||
import type {
|
||||
VerificationInputReportItem,
|
||||
VerificationInputReportResponse,
|
||||
VerificationInputReportTypeCountingInfo,
|
||||
} from "@/entities/statistics/model/verification-input-report-interface";
|
||||
import { useAlertStore, useMenuStore } from "@/shared/lib";
|
||||
import {
|
||||
findMenuItemByUrlPath,
|
||||
resolveInputPageNavigationFromStatsCd,
|
||||
} from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import {
|
||||
Badge,
|
||||
type BadgeProps,
|
||||
Label,
|
||||
LoadingWithAnimation,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
Label,
|
||||
} from "@common/components";
|
||||
import { CodeSelectWithAll } from "@/entities/code";
|
||||
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
type VerificationReportTabKey = "NV" | "IMR" | "HB";
|
||||
|
||||
const VERIFICATION_REPORT_TABS: { value: VerificationReportTabKey; label: string }[] = [
|
||||
{ value: "NV", label: "일반" },
|
||||
{ value: "IMR", label: "I-MR" },
|
||||
{ value: "HB", label: "HB" },
|
||||
{ value: "IMR", label: "시계열" },
|
||||
{ value: "HB", label: "모집단" },
|
||||
];
|
||||
|
||||
const TYPE_CODE_COLOR_MAP: Record<string, BadgeProps["color"]> = {
|
||||
@@ -52,8 +53,8 @@ const TYPE_CODE_COLOR_MAP: Record<string, BadgeProps["color"]> = {
|
||||
|
||||
const TYPE_CODE_NAME_MAP: Record<string, string> = {
|
||||
NV: "일반",
|
||||
IMR: "I-MR",
|
||||
HB: "HB",
|
||||
IMR: "시계열",
|
||||
HB: "모집단",
|
||||
};
|
||||
|
||||
const PAGE_SIZE = 100;
|
||||
@@ -378,8 +379,8 @@ const VerificationSelectionReport = ({
|
||||
<div className="flex justify-between text-lg text-muted-foreground">
|
||||
<span>{formatReportItemRegion(item)}</span>
|
||||
<span>
|
||||
현재: {item.nowVlCn ?? "-"} / 비교:{" "}
|
||||
{item.compVlCn ?? "-"}
|
||||
현재: {item.nowCn ?? "-"} / 비교:{" "}
|
||||
{item.cmprCn ?? "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,56 @@
|
||||
import {
|
||||
type RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
|
||||
import { flushSync } from "react-dom";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { ValueType } from "realgrid";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { statisticsInputExcretaTreatmentFacilityApi } from "@/entities/statistics/api/input/statistics-input-excreta-treatment-facility-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
EXCRETA_TREATMENT_FACILITY_STATS_CD,
|
||||
type ExcretaTreatmentFacilityItem,
|
||||
type ExcretaTreatmentFacilityRequestParams,
|
||||
type ExcretaTreatmentFacilitySaveItem,
|
||||
type ExcretaTreatmentFacilityVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-excreta-treatment-facility-interface";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type {
|
||||
ApiResponse,
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sgg,
|
||||
Sido,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
resolveNoApplicableForRequest,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
import AddArticleButton from "@/components/button/AddArticleButton";
|
||||
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
||||
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
|
||||
@@ -24,49 +60,24 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
import { ExcretaTreatFactInpColumn } from "@/components/util/column/input/ExcretaTreatFactInpColumn";
|
||||
import type {
|
||||
CellStyleCallbackParams,
|
||||
CellTooltipCallbackParams,
|
||||
ColumnConfig,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { ExcretaTreatFactInpColumn } from "@/components/util/column/input/ExcretaTreatFactInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputExcretaTreatmentFacilityApi } from "@/entities/statistics/api/input/statistics-input-excreta-treatment-facility-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import {
|
||||
EXCRETA_TREATMENT_FACILITY_STATS_CD,
|
||||
type ExcretaTreatmentFacilityItem,
|
||||
type ExcretaTreatmentFacilityRequestParams,
|
||||
type ExcretaTreatmentFacilitySaveItem,
|
||||
type ExcretaTreatmentFacilityVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-excreta-treatment-facility-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type {
|
||||
ApiResponse,
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
Sgg,
|
||||
SewerageStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: ExcretaTreatmentFacilityVerificationResult;
|
||||
@@ -82,8 +93,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -91,10 +102,22 @@ const computeTabEnterable = (
|
||||
return periodEnterable;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || "")
|
||||
? Boolean(values?.gugun && values.gugun !== "ALL")
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildNoApplicableForRequest(
|
||||
values: SearchFormValues | null,
|
||||
checked: boolean,
|
||||
isSaveListEmpty: boolean,
|
||||
): boolean {
|
||||
return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty);
|
||||
}
|
||||
|
||||
function computeTabToolbarSnapshot(
|
||||
@@ -107,8 +130,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -128,9 +151,8 @@ function buildExcretaTreatmentFacilitySearchParam(
|
||||
const fcltNm = values.fctName?.trim();
|
||||
return {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
...(fcltNm ? { fcltNm } : {}),
|
||||
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
||||
};
|
||||
@@ -191,13 +213,6 @@ function mapExcretaTreatmentFacilityListToGridRows(
|
||||
});
|
||||
}
|
||||
|
||||
function nullableTrimmedString(
|
||||
value: string | number | null | undefined,
|
||||
): string | null {
|
||||
const s = String(value ?? "").trim();
|
||||
return s === "" ? null : s;
|
||||
}
|
||||
|
||||
function toGridNumber(value: string | number | null | undefined): number {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
@@ -210,9 +225,9 @@ function toApiYmd(value: string | number | Date | null | undefined): string | nu
|
||||
function resolveExcretaTreatmentFacilityAdmdst(
|
||||
row: Record<string, string | number>,
|
||||
sggOptions: Sgg[],
|
||||
): { admdstCd: string; admdstNm: string } {
|
||||
const existingCd = String(row.admdstCd ?? "").trim();
|
||||
const existingNm = String(row.admdstNm ?? "").trim();
|
||||
): { admdstCd: string | null; admdstNm: string | null } {
|
||||
const existingCd = nullableTrimmedString(row.admdstCd);
|
||||
const existingNm = nullableTrimmedString(row.admdstNm);
|
||||
if (existingCd && existingNm) {
|
||||
return { admdstCd: existingCd, admdstNm: existingNm };
|
||||
}
|
||||
@@ -221,7 +236,10 @@ function resolveExcretaTreatmentFacilityAdmdst(
|
||||
if (matched) {
|
||||
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
|
||||
}
|
||||
return { admdstCd: existingCd, admdstNm: existingNm || sggNm };
|
||||
return {
|
||||
admdstCd: existingCd,
|
||||
admdstNm: existingNm ?? nullableTrimmedString(sggNm),
|
||||
};
|
||||
}
|
||||
|
||||
/** 그리드 행 → ST00070001 저장/삭제 API 항목 */
|
||||
@@ -236,38 +254,38 @@ function buildExcretaTreatmentFacilitySaveItem(
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
fcltSn: String(row.fcltSn ?? "").trim(),
|
||||
fcltNm: String(row.fcltNm ?? ""),
|
||||
fcltPstnCn: String(row.fcltPstnCn ?? ""),
|
||||
telNo: String(row.telNo ?? ""),
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
fcltNm: nullableTrimmedString(row.fcltNm),
|
||||
fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
|
||||
telnoCn: nullableTrimmedString(row.telnoCn),
|
||||
cmptnStrtYmd: toApiYmd(row.cmptnStrtYmd),
|
||||
oprtngStrtYmd: toApiYmd(row.oprtngStrtYmd),
|
||||
wholPjtcoAmt: toGridNumber(row.wholPjtcoAmt),
|
||||
prcsFcltCpct: toGridNumber(row.prcsFcltCpct),
|
||||
cpct: toGridNumber(row.cpct),
|
||||
oprtngDayCnt: toGridNumber(row.oprtngDayCnt),
|
||||
prcsMthdExpln: String(row.prcsMthdExpln ?? ""),
|
||||
linkTmplntCn: String(row.linkTmplntCn ?? ""),
|
||||
fcltCpct: toGridNumber(row.fcltCpct),
|
||||
excrFcltPrcqty: toGridNumber(row.excrFcltPrcqty),
|
||||
fcltOprtngDayCnt: toGridNumber(row.fcltOprtngDayCnt),
|
||||
prcsExpln: nullableTrimmedString(row.prcsExpln),
|
||||
linkTmplntCn: nullableTrimmedString(row.linkTmplntCn),
|
||||
linkPrcsQnt: toGridNumber(row.linkPrcsQnt),
|
||||
inflwBodWqVl: toGridNumber(row.inflwBodWqVl),
|
||||
inflwTocWqVl: toGridNumber(row.inflwTocWqVl),
|
||||
inflwSsWqVl: toGridNumber(row.inflwSsWqVl),
|
||||
inflwTnWqVl: toGridNumber(row.inflwTnWqVl),
|
||||
inflwTpWqVl: toGridNumber(row.inflwTpWqVl),
|
||||
inflwClfgCnt: toGridNumber(row.inflwClfgCnt),
|
||||
dschgBodWqVl: toGridNumber(row.dschgBodWqVl),
|
||||
dschgTocWqVl: toGridNumber(row.dschgTocWqVl),
|
||||
dschgSsWqVl: toGridNumber(row.dschgSsWqVl),
|
||||
dschgTnWqVl: toGridNumber(row.dschgTnWqVl),
|
||||
dschgTpWqVl: toGridNumber(row.dschgTpWqVl),
|
||||
dschgClfgCnt: toGridNumber(row.dschgClfgCnt),
|
||||
wtsysNm: String(row.wtsysNm ?? ""),
|
||||
trbtrNm: String(row.trbtrNm ?? ""),
|
||||
msirbCn: String(row.msirbCn ?? ""),
|
||||
ssirbCn: String(row.ssirbCn ?? ""),
|
||||
operMthdCdCn: String(row.operMthdCdCn ?? ""),
|
||||
inflwWqBod: toGridNumber(row.inflwWqBod),
|
||||
inflwWqTorgcb: toGridNumber(row.inflwWqTorgcb),
|
||||
inflwWqSpsld: toGridNumber(row.inflwWqSpsld),
|
||||
inflwWqDtn: toGridNumber(row.inflwWqDtn),
|
||||
inflwWqTph: toGridNumber(row.inflwWqTph),
|
||||
fcltInflwClfgCnt: toGridNumber(row.fcltInflwClfgCnt),
|
||||
dschgWqBod: toGridNumber(row.dschgWqBod),
|
||||
dschgWqTorgcb: toGridNumber(row.dschgWqTorgcb),
|
||||
dschgWqSpsld: toGridNumber(row.dschgWqSpsld),
|
||||
dschgWqDtn: toGridNumber(row.dschgWqDtn),
|
||||
dschgWqTph: toGridNumber(row.dschgWqTph),
|
||||
fcltDschgClfgCnt: toGridNumber(row.fcltDschgClfgCnt),
|
||||
fcltWtsysNm: nullableTrimmedString(row.fcltWtsysNm),
|
||||
fcltTrbtrNm: nullableTrimmedString(row.fcltTrbtrNm),
|
||||
msirbCn: nullableTrimmedString(row.msirbCn),
|
||||
ssirbCn: nullableTrimmedString(row.ssirbCn),
|
||||
operCn: nullableTrimmedString(row.operCn),
|
||||
wholNope: toGridNumber(row.wholNope),
|
||||
sftyAcdntEvntcsCnt: toGridNumber(row.sftyAcdntEvntcsCnt),
|
||||
sftyAcdntNocs: toGridNumber(row.sftyAcdntNocs),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -290,11 +308,11 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
function createEmptyExcretaTreatmentFacilityRow(
|
||||
@@ -316,6 +334,8 @@ const EXCRETA_TREATMENT_FACILITY_NAME_GUIDE_MESSAGE =
|
||||
|
||||
// 하수도 통계 관리 > 07. 분뇨처리시설
|
||||
const ExcretaTreatmentFacilityInp = () => {
|
||||
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
@@ -338,6 +358,8 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
|
||||
const [statsCd, setStatsCd] = useState("");
|
||||
const [isVerificationFailed, setIsVerificationFailed] = useState(false);
|
||||
const [footerVerificationResult, setFooterVerificationResult] =
|
||||
useState<VerificationResultLikeMap | null>(null);
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
||||
@@ -356,7 +378,10 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
const sggOptionsSidoRef = useRef<string | null>(null);
|
||||
const [searchLoaded, setSearchLoaded] = useState(false);
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.EXCRETA_TREATMENT_FACILITY) return;
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
try {
|
||||
@@ -382,7 +407,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [activeTabId]);
|
||||
|
||||
const loadSggOptions = useCallback(async (sido: string): Promise<Sgg[]> => {
|
||||
if (!sido || sido === "ALL") {
|
||||
@@ -402,7 +427,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const enterable =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = ExcretaTreatFactInpColumn.columns ?? [];
|
||||
const base = enterable
|
||||
? rawColumns
|
||||
@@ -430,7 +455,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
}, [statusInfo, inputPeriodEnterable, isAdminInfo, sggOptions, columnGuidesByEngCol]);
|
||||
|
||||
const canEditGrid =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
|
||||
const getCurrentRows = (): Record<string, string | number>[] =>
|
||||
gridGetRowsRef.current?.() ?? rowData;
|
||||
@@ -466,13 +491,18 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
statusInfo: nextStatusInfo,
|
||||
excretaTreatmentFacilityList,
|
||||
} = response.data;
|
||||
const list = excretaTreatmentFacilityList ?? [];
|
||||
const list = filterBySelectedAdmdstCd(
|
||||
excretaTreatmentFacilityList ?? [],
|
||||
sidoListRef.current,
|
||||
values,
|
||||
);
|
||||
const gridRows = mapExcretaTreatmentFacilityListToGridRows(list, commonInfo.ctpvNm);
|
||||
|
||||
setRowData(gridRows);
|
||||
setStatusInfo(nextStatusInfo);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
setFooterVerificationResult(commonInfo.verificationResult ?? null);
|
||||
if (overrides?.mode !== "PRE") {
|
||||
setIsNotApplicable(gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
@@ -532,6 +562,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
setStatusInfo(null);
|
||||
setStatsCd("");
|
||||
setIsVerificationFailed(false);
|
||||
setFooterVerificationResult(null);
|
||||
setIsNotApplicable(false);
|
||||
setInputPeriodEnterable(true);
|
||||
setSggOptions([]);
|
||||
@@ -581,14 +612,10 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
|
||||
const userInfo = await getUserInfo();
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD,
|
||||
};
|
||||
|
||||
@@ -622,7 +649,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
if (!(await confirm("저장 하시겠습니까?"))) return;
|
||||
|
||||
commitGrid();
|
||||
const noApplicable = isNotApplicable;
|
||||
const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
|
||||
const currentData = getCurrentRows();
|
||||
const saveList = noApplicable
|
||||
? []
|
||||
@@ -633,7 +660,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
.filter((row) => String(row.fcltSn ?? "").trim() !== "")
|
||||
.map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions));
|
||||
|
||||
if (!noApplicable && saveList.length === 0) {
|
||||
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -646,12 +673,8 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
{
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
noApplicable,
|
||||
saveList,
|
||||
@@ -686,9 +709,9 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const noApplicable = isNotApplicable;
|
||||
const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
|
||||
const currentData = getCurrentRows();
|
||||
if (!noApplicable && currentData.length === 0) {
|
||||
if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -713,7 +736,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
}
|
||||
|
||||
commitGrid();
|
||||
const noApplicable = isNotApplicable;
|
||||
const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
|
||||
const currentData = getCurrentRows();
|
||||
const submitList = noApplicable
|
||||
? []
|
||||
@@ -724,19 +747,15 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
.filter((row) => String(row.fcltSn ?? "").trim() !== "")
|
||||
.map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions));
|
||||
|
||||
if (!noApplicable && submitList.length === 0) {
|
||||
if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
const statsCdForSubmit = statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD;
|
||||
const verificationQuery = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
statsCd: statsCdForSubmit,
|
||||
};
|
||||
@@ -755,20 +774,16 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
{
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userUuid: userInfo.userUnqId,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
noApplicable,
|
||||
submitList,
|
||||
@@ -786,15 +801,16 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
|
||||
if (finalStatus === "5") {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel("IMR 검증 진행 중...");
|
||||
setGlobalProgressLabel("시계열 검증 진행 중...");
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "",
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForSubmit,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
statusInfo?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
statusInfo?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -941,19 +957,19 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR"
|
||||
? "IMR검증 - "
|
||||
? "시계열 검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "HB검증 -";
|
||||
: "모집단 검증 -";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
};
|
||||
|
||||
const searchContextForToolbar = lastSearchedValues ?? searchValues;
|
||||
const searchContextForToolbar = lastSearchedValues;
|
||||
const toolbarSnapshot = computeTabToolbarSnapshot(
|
||||
statusInfo,
|
||||
rowData.length > 0,
|
||||
@@ -973,23 +989,23 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
hasSearched &&
|
||||
toolbarGroupAllowed &&
|
||||
(showSaveInToolbar || showEnterableToolbarButtons);
|
||||
const tabEnterableForGrid =
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
const showNotApplicable =
|
||||
searchLoaded &&
|
||||
hasSearched &&
|
||||
rowData.length === 0 &&
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable);
|
||||
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) &&
|
||||
submitOk;
|
||||
|
||||
const verificationQueryContext = (() => {
|
||||
const ctx = lastSearchedValues ?? searchValues;
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -1005,6 +1021,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName={true}
|
||||
@@ -1013,7 +1030,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="card !py-0">
|
||||
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
@@ -1024,18 +1041,18 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void getLastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{statusInfo?.laeSbmsnSttsCd === "2" && (
|
||||
{statusInfo?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showArticleToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="card !py-0">
|
||||
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
||||
{showNotApplicable && (
|
||||
<NotApplicableCheckButton
|
||||
@@ -1043,10 +1060,10 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
setIsCheck={setIsNotApplicable}
|
||||
/>
|
||||
)}
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
</div>
|
||||
</div>
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10 [data-theme='black']:bg-transparent">
|
||||
@@ -1055,14 +1072,14 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<DataGrid
|
||||
<DataGrid
|
||||
key={canEditGrid ? "enterable" : "readonly"}
|
||||
getGridRef={gridRef as RefObject<RealGridReact | null>}
|
||||
columnInfo={columnInfo}
|
||||
rowStartNum={ExcretaTreatFactInpColumn.rowStartNum}
|
||||
fixedColumns={ExcretaTreatFactInpColumn.fixedColumns}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
getGridDataRef={gridGetRowsRef}
|
||||
showHeaderCheckbox
|
||||
statusInfo={statusInfo}
|
||||
@@ -1072,6 +1089,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={footerVerificationResult}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import {
|
||||
@@ -248,7 +249,7 @@ export function MainFacilityMappingSaveDialog({
|
||||
setIsSubmitting(true);
|
||||
await onConfirm?.({
|
||||
prxyBzentyNmCn: prxyBzentyNmCn.trim(),
|
||||
cnsgnCstVl,
|
||||
cnsgnCstVl: cnsgnCstVl,
|
||||
wholNope,
|
||||
prxyCtrtCn: prxyCtrtCnParam,
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore } from "@/shared/lib";
|
||||
import { useAlertStore, useGlobalProgressStore, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
@@ -10,20 +10,20 @@ import { statisticsInputPrivateSewageFacilityApi } from "@/entities/statistics/a
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
PrivateSewageDesigStatusSaveItem,
|
||||
PrivateSewageFacilityVerificationResult,
|
||||
PrivateSewageDesigStatusItem,
|
||||
PrivateSewageDesigStatusSaveItem,
|
||||
PrivateSewageDesigStatusSaveRequest,
|
||||
PrivateSewageDesigStatusSubmitRequest,
|
||||
PrivateSewageFacilityVerificationResult,
|
||||
SepticTankStatusItem,
|
||||
SepticTankStatusSaveItem,
|
||||
SepticTankStatusSaveRequest,
|
||||
SepticTankStatusSubmitRequest,
|
||||
SewageTreatFactStatusItem,
|
||||
SewageTreatFactStatusRequestParams,
|
||||
SewageTreatFactStatusSaveItem,
|
||||
SewageTreatFactStatusSaveRequest,
|
||||
SewageTreatFactStatusSubmitRequest,
|
||||
SepticTankStatusItem,
|
||||
SewageTreatFactStatusRequestParams,
|
||||
} from "@/entities/statistics/model/input/statistics-input-private-sewage-facility-interface";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
@@ -31,7 +31,15 @@ import type {
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sido,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
@@ -39,25 +47,31 @@ import { getUserInfo } from "../../../../../../packages/common/components/src/ap
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { PrivateSewageDesigStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/PrivateSewageDesigStatusInpColumn";
|
||||
import { SepticTankStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SepticTankStatusInpColumn";
|
||||
import { SewageTreatFactStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SewageTreatFactStatusInpColumn";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
const PrivateSewageFacilityInpTabList = [
|
||||
SewageTreatFactStatusInpColumn,
|
||||
@@ -74,10 +88,6 @@ const PRIVATE_SEWAGE_FACILITY_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY: Record<string, s
|
||||
[PrivateSewageDesigStatusInpColumn.tabKey]: "ST00080003",
|
||||
};
|
||||
|
||||
const PRIVATE_SEWAGE_FACILITY_COLUMN_GUIDE_STATS_CDS = Array.from(
|
||||
new Set(Object.values(PRIVATE_SEWAGE_FACILITY_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY)),
|
||||
);
|
||||
|
||||
type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: PrivateSewageFacilityVerificationResult;
|
||||
};
|
||||
@@ -99,9 +109,8 @@ function buildPrivateSewageFacilitySearchParam(
|
||||
): SewageTreatFactStatusRequestParams {
|
||||
return {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
||||
};
|
||||
}
|
||||
@@ -110,8 +119,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -119,10 +128,14 @@ const computeTabEnterable = (
|
||||
return periodEnterable;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || "")
|
||||
? Boolean(values?.gugun && values.gugun !== "ALL")
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function computeTabToolbarSnapshot(
|
||||
@@ -135,8 +148,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -158,8 +171,8 @@ function tabRowDataExists(
|
||||
|
||||
function mapRegionalListToGridRows<
|
||||
T extends { verificationResult?: PrivateSewageFacilityVerificationResult },
|
||||
>(list: T[], ctpvNm: string): GridRowWithVerification[] {
|
||||
return list.map((item) => {
|
||||
>(list: T[], ctpvNm: string, values?: SearchFormValues | null): GridRowWithVerification[] {
|
||||
return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
|
||||
const { verificationResult, ...rest } = item;
|
||||
const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification;
|
||||
if (verificationResult) row.verificationResult = verificationResult;
|
||||
@@ -167,17 +180,6 @@ function mapRegionalListToGridRows<
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeGridString(value: unknown): string {
|
||||
if (value === null || value === undefined) return "";
|
||||
return typeof value === "string" ? value : String(value);
|
||||
}
|
||||
|
||||
function normalizeGridRmrk(value: unknown): string | null {
|
||||
if (value === null || value === undefined) return null;
|
||||
const s = typeof value === "string" ? value : String(value);
|
||||
return s.trim() === "" ? null : s;
|
||||
}
|
||||
|
||||
function buildColumnFormatNumber(
|
||||
columns: { fieldName?: string; dataFormat?: string }[],
|
||||
) {
|
||||
@@ -206,23 +208,23 @@ function buildSewageTreatFactStatusSaveList(
|
||||
const formatNumber = buildColumnFormatNumber(SewageTreatFactStatusInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
step1StaaPrcsQnt: formatNumber(d.step1StaaPrcsQnt, "step1StaaPrcsQnt"),
|
||||
step2StaaPrcsQnt: formatNumber(d.step2StaaPrcsQnt, "step2StaaPrcsQnt"),
|
||||
step3StaaPrcsQnt: formatNumber(d.step3StaaPrcsQnt, "step3StaaPrcsQnt"),
|
||||
step4StaaPrcsQnt: formatNumber(d.step4StaaPrcsQnt, "step4StaaPrcsQnt"),
|
||||
step5StaaPrcsQnt: formatNumber(d.step5StaaPrcsQnt, "step5StaaPrcsQnt"),
|
||||
step6StaaPrcsQnt: formatNumber(d.step6StaaPrcsQnt, "step6StaaPrcsQnt"),
|
||||
step7StaaPrcsQnt: formatNumber(d.step7StaaPrcsQnt, "step7StaaPrcsQnt"),
|
||||
step1StaaExcpPrcsQnt: formatNumber(d.step1StaaExcpPrcsQnt, "step1StaaExcpPrcsQnt"),
|
||||
step2StaaExcpPrcsQnt: formatNumber(d.step2StaaExcpPrcsQnt, "step2StaaExcpPrcsQnt"),
|
||||
step3StaaExcpPrcsQnt: formatNumber(d.step3StaaExcpPrcsQnt, "step3StaaExcpPrcsQnt"),
|
||||
step4StaaExcpPrcsQnt: formatNumber(d.step4StaaExcpPrcsQnt, "step4StaaExcpPrcsQnt"),
|
||||
step5StaaExcpPrcsQnt: formatNumber(d.step5StaaExcpPrcsQnt, "step5StaaExcpPrcsQnt"),
|
||||
step6StaaExcpPrcsQnt: formatNumber(d.step6StaaExcpPrcsQnt, "step6StaaExcpPrcsQnt"),
|
||||
step7StaaExcpPrcsQnt: formatNumber(d.step7StaaExcpPrcsQnt, "step7StaaExcpPrcsQnt"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
frlvlStaaPrcqty: formatNumber(d.frlvlStaaPrcqty, "frlvlStaaPrcqty"),
|
||||
sclvlStaaPrcqty: formatNumber(d.sclvlStaaPrcqty, "sclvlStaaPrcqty"),
|
||||
thlvlStaaPrcqty: formatNumber(d.thlvlStaaPrcqty, "thlvlStaaPrcqty"),
|
||||
folvlStaaPrcqty: formatNumber(d.folvlStaaPrcqty, "folvlStaaPrcqty"),
|
||||
filvlStaaPrcqty: formatNumber(d.filvlStaaPrcqty, "filvlStaaPrcqty"),
|
||||
sxlvlStaaPrcqty: formatNumber(d.sxlvlStaaPrcqty, "sxlvlStaaPrcqty"),
|
||||
svlvlStaaPrcqty: formatNumber(d.svlvlStaaPrcqty, "svlvlStaaPrcqty"),
|
||||
frlvlStaaOtsdPrcqty: formatNumber(d.frlvlStaaOtsdPrcqty, "frlvlStaaOtsdPrcqty"),
|
||||
sclvlStaaOtsdPrcqty: formatNumber(d.sclvlStaaOtsdPrcqty, "sclvlStaaOtsdPrcqty"),
|
||||
thlvlStaaOtsdPrcqty: formatNumber(d.thlvlStaaOtsdPrcqty, "thlvlStaaOtsdPrcqty"),
|
||||
folvlStaaOtsdPrcqty: formatNumber(d.folvlStaaOtsdPrcqty, "folvlStaaOtsdPrcqty"),
|
||||
filvlStaaOtsdPrcqty: formatNumber(d.filvlStaaOtsdPrcqty, "filvlStaaOtsdPrcqty"),
|
||||
sxlvlStaaOtsdPrcqty: formatNumber(d.sxlvlStaaOtsdPrcqty, "sxlvlStaaOtsdPrcqty"),
|
||||
svlvlStaaOtsdPrcqty: formatNumber(d.svlvlStaaOtsdPrcqty, "svlvlStaaOtsdPrcqty"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -232,37 +234,37 @@ function buildSepticTankStatusSaveList(
|
||||
const formatNumber = buildColumnFormatNumber(SepticTankStatusInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
staaInsdSttkNprsflCnt: formatNumber(d.staaInsdSttkNprsflCnt, "staaInsdSttkNprsflCnt"),
|
||||
step1StaaInsdSttkFrcCnt: formatNumber(d.step1StaaInsdSttkFrcCnt, "step1StaaInsdSttkFrcCnt"),
|
||||
staaInsdSprktpFltrtbCnt: formatNumber(d.staaInsdSprktpFltrtbCnt, "staaInsdSprktpFltrtbCnt"),
|
||||
staaInsdAertMthdCnt: formatNumber(d.staaInsdAertMthdCnt, "staaInsdAertMthdCnt"),
|
||||
staaInsdCntcAertCnt: formatNumber(d.staaInsdCntcAertCnt, "staaInsdCntcAertCnt"),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
staaInsdSpctnkNatrCnt: formatNumber(d.staaInsdSpctnkNatrCnt, "staaInsdSpctnkNatrCnt"),
|
||||
staaInsdSpctnkFrcCnt: formatNumber(d.staaInsdSpctnkFrcCnt, "staaInsdSpctnkFrcCnt"),
|
||||
staaInsdSprklFiltrCnt: formatNumber(d.staaInsdSprklFiltrCnt, "staaInsdSprklFiltrCnt"),
|
||||
staaInsdAirCnt: formatNumber(d.staaInsdAirCnt, "staaInsdAirCnt"),
|
||||
staaInsdCntcAirCnt: formatNumber(d.staaInsdCntcAirCnt, "staaInsdCntcAirCnt"),
|
||||
staaInsdEtcCnt: formatNumber(d.staaInsdEtcCnt, "staaInsdEtcCnt"),
|
||||
step1StaaInsdNope: formatNumber(d.step1StaaInsdNope, "step1StaaInsdNope"),
|
||||
step2StaaInsdNope: formatNumber(d.step2StaaInsdNope, "step2StaaInsdNope"),
|
||||
step3StaaInsdNope: formatNumber(d.step3StaaInsdNope, "step3StaaInsdNope"),
|
||||
step4StaaInsdNope: formatNumber(d.step4StaaInsdNope, "step4StaaInsdNope"),
|
||||
step5StaaInsdNope: formatNumber(d.step5StaaInsdNope, "step5StaaInsdNope"),
|
||||
step6StaaInsdNope: formatNumber(d.step6StaaInsdNope, "step6StaaInsdNope"),
|
||||
step7StaaInsdNope: formatNumber(d.step7StaaInsdNope, "step7StaaInsdNope"),
|
||||
step8StaaInsdNope: formatNumber(d.step8StaaInsdNope, "step8StaaInsdNope"),
|
||||
staaExcpSttkNprsflCnt: formatNumber(d.staaExcpSttkNprsflCnt, "staaExcpSttkNprsflCnt"),
|
||||
step1StaaExcpSttkFrcCnt: formatNumber(d.step1StaaExcpSttkFrcCnt, "step1StaaExcpSttkFrcCnt"),
|
||||
staaExcpSprktpFltrtbCnt: formatNumber(d.staaExcpSprktpFltrtbCnt, "staaExcpSprktpFltrtbCnt"),
|
||||
staaExcpAertMthdCnt: formatNumber(d.staaExcpAertMthdCnt, "staaExcpAertMthdCnt"),
|
||||
staaExcpCntcAertCnt: formatNumber(d.staaExcpCntcAertCnt, "staaExcpCntcAertCnt"),
|
||||
staaExcpEtcCnt: formatNumber(d.staaExcpEtcCnt, "staaExcpEtcCnt"),
|
||||
step1StaaExcpNope: formatNumber(d.step1StaaExcpNope, "step1StaaExcpNope"),
|
||||
step2StaaExcpNope: formatNumber(d.step2StaaExcpNope, "step2StaaExcpNope"),
|
||||
step3StaaExcpNope: formatNumber(d.step3StaaExcpNope, "step3StaaExcpNope"),
|
||||
step4StaaExcpNope: formatNumber(d.step4StaaExcpNope, "step4StaaExcpNope"),
|
||||
step5StaaExcpNope: formatNumber(d.step5StaaExcpNope, "step5StaaExcpNope"),
|
||||
step6StaaExcpNope: formatNumber(d.step6StaaExcpNope, "step6StaaExcpNope"),
|
||||
step7StaaExcpNope: formatNumber(d.step7StaaExcpNope, "step7StaaExcpNope"),
|
||||
step8StaaExcpNope: formatNumber(d.step8StaaExcpNope, "step8StaaExcpNope"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
frlvlStaaInsdNope: formatNumber(d.frlvlStaaInsdNope, "frlvlStaaInsdNope"),
|
||||
sclvlStaaInsdNope: formatNumber(d.sclvlStaaInsdNope, "sclvlStaaInsdNope"),
|
||||
thlvlStaaInsdNope: formatNumber(d.thlvlStaaInsdNope, "thlvlStaaInsdNope"),
|
||||
folvlStaaInsdNope: formatNumber(d.folvlStaaInsdNope, "folvlStaaInsdNope"),
|
||||
filvlStaaInsdNope: formatNumber(d.filvlStaaInsdNope, "filvlStaaInsdNope"),
|
||||
sxlvlStaaInsdNope: formatNumber(d.sxlvlStaaInsdNope, "sxlvlStaaInsdNope"),
|
||||
svlvlStaaInsdNope: formatNumber(d.svlvlStaaInsdNope, "svlvlStaaInsdNope"),
|
||||
eilvlStaaInsdNope: formatNumber(d.eilvlStaaInsdNope, "eilvlStaaInsdNope"),
|
||||
staaOtsdSpctnkNatrCnt: formatNumber(d.staaOtsdSpctnkNatrCnt, "staaOtsdSpctnkNatrCnt"),
|
||||
staaOtsdSpctnkFrcCnt: formatNumber(d.staaOtsdSpctnkFrcCnt, "staaOtsdSpctnkFrcCnt"),
|
||||
staaOtsdSprklFiltrCnt: formatNumber(d.staaOtsdSprklFiltrCnt, "staaOtsdSprklFiltrCnt"),
|
||||
staaOtsdAirCnt: formatNumber(d.staaOtsdAirCnt, "staaOtsdAirCnt"),
|
||||
staaOtsdCntcAirCnt: formatNumber(d.staaOtsdCntcAirCnt, "staaOtsdCntcAirCnt"),
|
||||
staaOtsdEtcCnt: formatNumber(d.staaOtsdEtcCnt, "staaOtsdEtcCnt"),
|
||||
frlvlStaaOtsdNope: formatNumber(d.frlvlStaaOtsdNope, "frlvlStaaOtsdNope"),
|
||||
sclvlStaaOtsdNope: formatNumber(d.sclvlStaaOtsdNope, "sclvlStaaOtsdNope"),
|
||||
thlvlStaaOtsdNope: formatNumber(d.thlvlStaaOtsdNope, "thlvlStaaOtsdNope"),
|
||||
folvlStaaOtsdNope: formatNumber(d.folvlStaaOtsdNope, "folvlStaaOtsdNope"),
|
||||
filvlStaaOtsdNope: formatNumber(d.filvlStaaOtsdNope, "filvlStaaOtsdNope"),
|
||||
sxlvlStaaOtsdNope: formatNumber(d.sxlvlStaaOtsdNope, "sxlvlStaaOtsdNope"),
|
||||
svlvlStaaOtsdNope: formatNumber(d.svlvlStaaOtsdNope, "svlvlStaaOtsdNope"),
|
||||
eilvlStaaOtsdNope: formatNumber(d.eilvlStaaOtsdNope, "eilvlStaaOtsdNope"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -272,18 +274,18 @@ function buildPrivateSewageDesigStatusSaveList(
|
||||
const formatNumber = buildColumnFormatNumber(PrivateSewageDesigStatusInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
indvSwagePbancRgnCnt: formatNumber(d.indvSwagePbancRgnCnt, "indvSwagePbancRgnCnt"),
|
||||
indvSwageDsgnScpLen: formatNumber(d.indvSwageDsgnScpLen, "indvSwageDsgnScpLen"),
|
||||
spctnkGrssFcltCnt: formatNumber(d.spctnkGrssFcltCnt, "spctnkGrssFcltCnt"),
|
||||
spctnkQnt: formatNumber(d.spctnkQnt, "spctnkQnt"),
|
||||
drtwPrcsFcltGrssFcltCnt: formatNumber(
|
||||
d.drtwPrcsFcltGrssFcltCnt,
|
||||
"drtwPrcsFcltGrssFcltCnt",
|
||||
spctnkWholFcltCnt: formatNumber(d.spctnkWholFcltCnt, "spctnkWholFcltCnt"),
|
||||
spctnkPrcqty: formatNumber(d.spctnkPrcqty, "spctnkPrcqty"),
|
||||
swgdplFcltWholFcltCnt: formatNumber(
|
||||
d.swgdplFcltWholFcltCnt,
|
||||
"swgdplFcltWholFcltCnt",
|
||||
),
|
||||
drtwPrcsFcltQnt: formatNumber(d.drtwPrcsFcltQnt, "drtwPrcsFcltQnt"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
swgdplFcltPrcqty: formatNumber(d.swgdplFcltPrcqty, "swgdplFcltPrcqty"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -305,11 +307,11 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
function resolvePrivateSewageFacilityTab(tabKey: string) {
|
||||
@@ -322,6 +324,8 @@ function resolvePrivateSewageFacilityTab(tabKey: string) {
|
||||
|
||||
// 하수도 통계 관리 > 08. 개인하수처리시설
|
||||
const PrivateSewageFacilityInp = () => {
|
||||
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
@@ -345,9 +349,15 @@ const PrivateSewageFacilityInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
|
||||
const [showSubmitDialog, setShowSubmitDialog] = useState(false);
|
||||
@@ -362,42 +372,30 @@ const PrivateSewageFacilityInp = () => {
|
||||
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
|
||||
activeTabRef.current = activeTab;
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.PRIVATE_SEWAGE_FACILITY) return;
|
||||
const tabKey = activeTab;
|
||||
if (!tabKey) return;
|
||||
if (Object.prototype.hasOwnProperty.call(columnGuidesByTab, tabKey)) return;
|
||||
const statsCd =
|
||||
PRIVATE_SEWAGE_FACILITY_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY[tabKey] ?? tabKey;
|
||||
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
PRIVATE_SEWAGE_FACILITY_COLUMN_GUIDE_STATS_CDS.map(async (statsCd) => {
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(statsCd);
|
||||
if (res.status !== "success" || !Array.isArray(res.data)) {
|
||||
return [statsCd, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [statsCd, next] as const;
|
||||
} catch {
|
||||
return [statsCd, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
const next = await fetchColumnGuidesRecord(statsCd);
|
||||
if (cancelled) return;
|
||||
const guidesByStatsCd = Object.fromEntries(entries);
|
||||
setColumnGuidesByTab(
|
||||
Object.fromEntries(
|
||||
Object.entries(PRIVATE_SEWAGE_FACILITY_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY).map(
|
||||
([tabKey, statsCd]) => [tabKey, guidesByStatsCd[statsCd] ?? {}],
|
||||
),
|
||||
),
|
||||
setColumnGuidesByTab((prev) =>
|
||||
Object.prototype.hasOwnProperty.call(prev, tabKey)
|
||||
? prev
|
||||
: { ...prev, [tabKey]: next },
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [activeTabId, activeTab, columnGuidesByTab]);
|
||||
|
||||
const commitToolbarSnapshotForLoadedTab = (
|
||||
tabKey: string,
|
||||
@@ -420,6 +418,13 @@ const PrivateSewageFacilityInp = () => {
|
||||
);
|
||||
return next;
|
||||
});
|
||||
if (searchContext) {
|
||||
setSearchContextByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, searchContext);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const applyRowMap = (
|
||||
@@ -462,6 +467,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
const gridRows = mapRegionalListToGridRows(
|
||||
(sewageTreatFactStatusList ?? []) as SewageTreatFactStatusItem[],
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -472,6 +478,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -490,6 +499,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
const gridRows = mapRegionalListToGridRows(
|
||||
(septicTankStatusList ?? []) as SepticTankStatusItem[],
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -500,6 +510,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -518,6 +531,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
const gridRows = mapRegionalListToGridRows(
|
||||
(privateSewageDesigStatusList ?? []) as PrivateSewageDesigStatusItem[],
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -528,6 +542,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -545,7 +562,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
setLastSearchedValues(null);
|
||||
@@ -587,7 +606,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
@@ -697,14 +718,10 @@ const PrivateSewageFacilityInp = () => {
|
||||
}
|
||||
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -785,28 +802,20 @@ const PrivateSewageFacilityInp = () => {
|
||||
const submitInfoBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
};
|
||||
|
||||
const verificationQuery = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
@@ -826,12 +835,12 @@ const PrivateSewageFacilityInp = () => {
|
||||
const userInfo = await getUserInfo();
|
||||
setGlobalProgressLabel("일반 검증 진행 중...");
|
||||
|
||||
let response: { status: string; message?: string; data?: { finalStatus?: string; nvpassed?: boolean } };
|
||||
let response: { status: string; message?: string; data?: { finalStatus?: string; nvpassed?: boolean; isHbPassed?: string } };
|
||||
|
||||
if (isSewageTreatTab) {
|
||||
const param: SewageTreatFactStatusSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSewageTreatFactStatusSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
@@ -842,7 +851,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
} else if (isSepticTankTab) {
|
||||
const param: SepticTankStatusSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSepticTankStatusSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
@@ -851,7 +860,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
} else {
|
||||
const param: PrivateSewageDesigStatusSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildPrivateSewageDesigStatusSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
@@ -871,14 +880,15 @@ const PrivateSewageFacilityInp = () => {
|
||||
|
||||
if (finalStatus === "5") {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel("IMR 검증 진행 중...");
|
||||
setGlobalProgressLabel("시계열 검증 진행 중...");
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "",
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -962,11 +972,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
const baseRequest = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -976,7 +982,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
if (isSewageTreatTab) {
|
||||
const param: SewageTreatFactStatusSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSewageTreatFactStatusSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1002,7 +1008,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
if (isSepticTankTab) {
|
||||
const param: SepticTankStatusSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSepticTankStatusSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1025,7 +1031,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
|
||||
const param: PrivateSewageDesigStatusSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildPrivateSewageDesigStatusSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1068,9 +1074,8 @@ const PrivateSewageFacilityInp = () => {
|
||||
const statsCd = tabStatsCdMap.get(activeTab);
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -1081,10 +1086,10 @@ const PrivateSewageFacilityInp = () => {
|
||||
for (const tab of PrivateSewageFacilityInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1101,19 +1106,20 @@ const PrivateSewageFacilityInp = () => {
|
||||
return map;
|
||||
}, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]);
|
||||
|
||||
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
||||
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
||||
const activeTabToolbar =
|
||||
toolbarSnapshotByTab.get(activeTab) ??
|
||||
computeTabToolbarSnapshot(
|
||||
activeTabStatus,
|
||||
tabRowDataExists(rowDataMap, activeTab),
|
||||
hasSearched,
|
||||
lastSearchedValues,
|
||||
searchContextForToolbar,
|
||||
inputPeriodEnterable,
|
||||
isAdminInfo,
|
||||
);
|
||||
const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } =
|
||||
activeTabToolbar;
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
|
||||
const verificationCellStyle = ({
|
||||
fieldName,
|
||||
@@ -1146,11 +1152,11 @@ const PrivateSewageFacilityInp = () => {
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR"
|
||||
? "IMR검증 - "
|
||||
? "시계열 검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "HB검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1172,6 +1178,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
@@ -1192,10 +1199,10 @@ const PrivateSewageFacilityInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void lastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
@@ -1227,11 +1234,11 @@ const PrivateSewageFacilityInp = () => {
|
||||
const tabKey = info.tabKey;
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
|
||||
return (
|
||||
@@ -1269,6 +1276,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useRef, useState, type Dispatch, type SetStateAction } from 'react';
|
||||
import { type Dispatch, type SetStateAction,useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useConfirm } from '@frontend-opensource/use-react-hooks';
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from '@/shared/lib';
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from '@/shared/lib';
|
||||
import { STATISTICS_INPUT_URL_PATH } from '@/shared/lib/stats-cd-input-navigation';
|
||||
import { useStatisticsInputPendingNavigation } from '@/shared/lib/use-statistics-input-pending-navigation';
|
||||
import { statisticsInputSewSupplyRateApi } from '@/entities/statistics/api/input/statistics-input-sew-supply-rate-service';
|
||||
@@ -23,7 +23,9 @@ import {
|
||||
type SewerageSupplyRateVerificationResult,
|
||||
} from '@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface';
|
||||
import type { StatisticsInputSubmitInfoFormData } from '@/entities/statistics/model/statistics-input-submit-info-schema';
|
||||
import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo } from '@/entities/statistics/model/statistics-interface';
|
||||
import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, Sido } from '@/entities/statistics/model/statistics-interface';
|
||||
import { filterBySelectedAdmdstCd, isGugunOkForToolbar, isSubmitOkForToolbar, resolveNoApplicableForRequest, toInputSearchRegion } from '@/entities/statistics';
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from '@common/components';
|
||||
|
||||
import { getUserInfo } from '../../../../../../packages/common/components/src/api';
|
||||
@@ -38,19 +40,22 @@ import SubmitButton from '@/components/button/SumitButton';
|
||||
import UpdateOperateDataButton from '@/components/button/UpdateOperateDataButton';
|
||||
import VerificationResultButton from '@/components/button/VerificationResultButton';
|
||||
import DataGrid from '@/components/module/DataGrid';
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from '@/components/module/post-submit-verification-results';
|
||||
import SearchForm from '@/components/module/SearchForm';
|
||||
import SplitDataGrid from '@/components/module/SplitDataGrid';
|
||||
import { openVerificationResultDialogIfNeeded } from '@/components/module/post-submit-verification-results';
|
||||
import { VerificationResultDialog } from '@/components/module/VerificationResultDialog';
|
||||
import { VerificationResultHistoryDialog } from '@/components/module/VerificationResultHistoryDialog';
|
||||
import type { CellStyleCallbackParams, ColumnInfo, ColumnInfoList } from '@/components/type/DataGrid.type';
|
||||
import type { SearchFormValues } from '@/components/type/SearchForm.type';
|
||||
import { doList } from '@/components/util/column/input/DoList';
|
||||
import { PublicSewageFactInpColumnList } from '@/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactInpColumn';
|
||||
import { fetchColumnGuidesRecord } from '@/components/util/grid/columnGuides';
|
||||
import { toGridRows } from '@/components/util/grid/gridFunction';
|
||||
import { patchVerificationFailedByTab } from '@/components/util/input/verification-failed-flag';
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { patchFooterVerificationByTab,patchVerificationFailedByTab } from '@/components/util/input/verification-failed-flag';
|
||||
import type { VerificationResultLikeMap } from '@/components/util/input/verification-result-display';
|
||||
import { nullableTrimmedString } from '@/components/util/input/nullableString';
|
||||
import { useAdminStore } from '@/store/admin-store';
|
||||
import { useTabStore } from '@/store/tab-store';
|
||||
|
||||
type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: SewerageSupplyRateVerificationResult;
|
||||
@@ -62,18 +67,30 @@ type TabToolbarSnapshot = {
|
||||
showActionToolbarCard: boolean;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || '')
|
||||
? Boolean(values?.gugun && values.gugun !== 'ALL')
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildNoApplicableForRequest(
|
||||
values: SearchFormValues | null,
|
||||
checked: boolean,
|
||||
isSaveListEmpty: boolean,
|
||||
): boolean {
|
||||
return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty);
|
||||
}
|
||||
|
||||
const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (['2', '3'].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -91,8 +108,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === '2';
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === '2';
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -122,11 +139,11 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error('IMR 검증 완료 대기 시간이 초과되었습니다.');
|
||||
throw new Error('시계열 검증 완료 대기 시간이 초과되었습니다.');
|
||||
}
|
||||
|
||||
type MainFacilityMappingSaveDraft = {
|
||||
@@ -144,10 +161,6 @@ type MainFacilityMappingSaveDraft = {
|
||||
initialPrxyCtrtCn: string;
|
||||
};
|
||||
|
||||
const PUBLIC_SEWAGE_FACILITY_COLUMN_GUIDE_TAB_KEYS = Array.from(
|
||||
new Set(PublicSewageFactInpColumnList.map((tab) => tab.tabKey).filter(Boolean)),
|
||||
);
|
||||
|
||||
/** 위탁계약기간: `yyyymmdd~yyyymmdd` (빈 값 허용). 하이픈 형식은 정규화 */
|
||||
function normalizePrxyCtrtCn(raw: unknown): string | null {
|
||||
if (raw === null || raw === undefined) return null;
|
||||
@@ -160,6 +173,10 @@ function normalizePrxyCtrtCn(raw: unknown): string | null {
|
||||
return s;
|
||||
}
|
||||
|
||||
function isMainFacilityRow(row: Record<string, string | number>): boolean {
|
||||
return String(row.isMainYn ?? '').toUpperCase() === 'Y';
|
||||
}
|
||||
|
||||
function isValidPrxyCtrtCn(value: string | null): boolean {
|
||||
if (value === null) return true;
|
||||
if (!/^\d{8}~\d{8}$/.test(value)) return false;
|
||||
@@ -181,6 +198,8 @@ function validatePrxyCtrtCnInRows(rows: Record<string, string | number>[]): stri
|
||||
|
||||
// 하수도 통계 관리 > 06. 공공하수처리시설
|
||||
const PublicSewageFacilityInp = () => {
|
||||
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
@@ -203,9 +222,15 @@ const PublicSewageFacilityInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
/** 탭키별 `engColNm -> columnGuide` 캐시 */
|
||||
const [columnGuidesByTab, setColumnGuidesByTab] = useState<Record<string, Record<string, string>>>({});
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<Map<string, TabToolbarSnapshot>>(() => new Map());
|
||||
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
|
||||
const [showDailog, setShowDailog] = useState<boolean>(false);
|
||||
@@ -244,10 +269,10 @@ const PublicSewageFacilityInp = () => {
|
||||
alert('먼저 조회를 진행해주세요.');
|
||||
return;
|
||||
}
|
||||
const treatAsNoApplicable = getNoApplicableForRequest() || isNotApplicable;
|
||||
const treatAsNoApplicable = getNoApplicableForRequest();
|
||||
const currentData =
|
||||
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
|
||||
if (!treatAsNoApplicable && (!currentData || currentData.length === 0)) {
|
||||
if (!treatAsNoApplicable && (!currentData || currentData.length === 0) && buildSubmitOkForToolbar(searchContextByTab.get(activeTab) ?? lastSearchedValues)) {
|
||||
alert('해당사항 없음을 체크해주세요.');
|
||||
return;
|
||||
}
|
||||
@@ -275,19 +300,13 @@ const PublicSewageFacilityInp = () => {
|
||||
return Number.isFinite(num) ? num : 0;
|
||||
};
|
||||
|
||||
const toNullableString = (v: unknown): string | null => {
|
||||
if (v === null || v === undefined) return null;
|
||||
const s = typeof v === 'string' ? v : String(v);
|
||||
return s.trim() === '' ? null : s;
|
||||
};
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: String(d.admdstCd ?? ''),
|
||||
admdstNm: String(d.admdstNm ?? ''),
|
||||
swtrmtFcltCd: String(d.swtrmtFcltCd ?? ''),
|
||||
swtrmtFcltNm: String(d.swtrmtFcltNm ?? ''),
|
||||
fcltSn: String(d.fcltSn ?? ''),
|
||||
fcltPstnCn: d.fcltPstnCn === '' ? null : toNullableString(d.fcltPstnCn),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(d.swtrmtFcltNm),
|
||||
fcltSn: nullableTrimmedString(d.fcltSn),
|
||||
fcltPstnCn: nullableTrimmedString(d.fcltPstnCn),
|
||||
inflwSwagePhysQnt: toNumber(d.inflwSwagePhysQnt),
|
||||
inflwSwageBoilQnt: toNumber(d.inflwSwageBoilQnt),
|
||||
inflwSwageAltdQnt: toNumber(d.inflwSwageAltdQnt),
|
||||
@@ -296,18 +315,19 @@ const PublicSewageFacilityInp = () => {
|
||||
prcsAltdCpct: toNumber(d.prcsAltdCpct),
|
||||
prxyCtrtCn: normalizePrxyCtrtCn(d.prxyCtrtCn),
|
||||
sftyAcdntEvntcsCnt: toNumber(d.sftyAcdntEvntcsCnt),
|
||||
rmrk: toNullableString(d.rmrk),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
};
|
||||
|
||||
const getNoApplicableForRequest = (): boolean => {
|
||||
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
||||
const searchContextForToolbar = lastSearchedValues ?? searchValues;
|
||||
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const enterable = computeTabEnterable(activeTabStatus, inputPeriodEnterable);
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const noApplicableVisible = showEnterableToolbarButtons && showIsNotApplicable;
|
||||
return noApplicableVisible ? Boolean(isNotApplicable) : false;
|
||||
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
||||
const currentData =
|
||||
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
|
||||
return buildNoApplicableForRequest(
|
||||
searchContextForToolbar,
|
||||
Boolean(isNotApplicable),
|
||||
buildFacilitySaveList(currentData).length === 0,
|
||||
);
|
||||
};
|
||||
|
||||
const isCheckedRow = (row: Record<string, string | number>): boolean => {
|
||||
@@ -315,6 +335,28 @@ const PublicSewageFacilityInp = () => {
|
||||
return v === 1 || v === '1' || v === 'Y' || v === 'y' || v === 'true';
|
||||
};
|
||||
|
||||
const isSelfMappingTarget = (
|
||||
row: Record<string, string | number>,
|
||||
mainFcltSn: string | null,
|
||||
): boolean => Boolean(mainFcltSn) && String(row.fcltSn ?? '') === mainFcltSn;
|
||||
|
||||
const hasCheckedSelfMapping = (
|
||||
rows: Record<string, string | number>[],
|
||||
mainFcltSn: string | null,
|
||||
): boolean => rows.some((r) => isSelfMappingTarget(r, mainFcltSn) && isCheckedRow(r));
|
||||
|
||||
/** 왼쪽에서 선택한 시설을 오른쪽에서 본인 체크하면 다른 시설은 해제 */
|
||||
const applyExclusiveSelfMappingChecks = (
|
||||
rows: Record<string, string | number>[],
|
||||
mainFcltSn: string | null,
|
||||
): Record<string, string | number>[] => {
|
||||
if (!hasCheckedSelfMapping(rows, mainFcltSn)) return rows;
|
||||
return rows.map((r) => ({
|
||||
...r,
|
||||
__checked: isSelfMappingTarget(r, mainFcltSn) ? 'Y' : '',
|
||||
}));
|
||||
};
|
||||
|
||||
const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
|
||||
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
|
||||
if (verificationBlocked) {
|
||||
@@ -330,7 +372,7 @@ const PublicSewageFacilityInp = () => {
|
||||
return;
|
||||
}
|
||||
const noApplicable = getNoApplicableForRequest();
|
||||
const treatAsNoApplicable = noApplicable || isNotApplicable;
|
||||
const treatAsNoApplicable = noApplicable;
|
||||
const currentData =
|
||||
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
|
||||
|
||||
@@ -341,7 +383,7 @@ const PublicSewageFacilityInp = () => {
|
||||
}
|
||||
|
||||
const submitList = treatAsNoApplicable ? [] : buildFacilitySaveList(currentData);
|
||||
if (!treatAsNoApplicable && submitList.length === 0) {
|
||||
if (!treatAsNoApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(activeTab) ?? lastSearchedValues)) {
|
||||
alert('해당사항 없음을 체크해주세요.');
|
||||
return;
|
||||
}
|
||||
@@ -354,18 +396,17 @@ const PublicSewageFacilityInp = () => {
|
||||
const requestData = {
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userUuid: userInfo.userUnqId,
|
||||
noApplicable,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
submitList,
|
||||
};
|
||||
|
||||
@@ -385,14 +426,15 @@ const PublicSewageFacilityInp = () => {
|
||||
|
||||
if (finalStatus === '5') {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel('IMR 검증 진행 중...');
|
||||
setGlobalProgressLabel('시계열 검증 진행 중...');
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? '',
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? '',
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? '',
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === '4' ? '4' : '2',
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === '4' ? '4' : '2',
|
||||
gen_verify_stts_cd: nvpassed ? '1' : '2',
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -416,9 +458,8 @@ const PublicSewageFacilityInp = () => {
|
||||
);
|
||||
await openVerificationResultDialogIfNeeded(
|
||||
{
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : (lastSearchedValues.gugun ?? ''),
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
statsCd: statsCdForTab,
|
||||
},
|
||||
@@ -433,9 +474,8 @@ const PublicSewageFacilityInp = () => {
|
||||
);
|
||||
await openVerificationResultDialogIfNeeded(
|
||||
{
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : (lastSearchedValues.gugun ?? ''),
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
statsCd: statsCdForTab,
|
||||
},
|
||||
@@ -457,9 +497,8 @@ const PublicSewageFacilityInp = () => {
|
||||
);
|
||||
await openVerificationResultDialogIfNeeded(
|
||||
{
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : (lastSearchedValues.gugun ?? ''),
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
statsCd: statsCdForTab,
|
||||
},
|
||||
@@ -489,7 +528,7 @@ const PublicSewageFacilityInp = () => {
|
||||
|
||||
if (activeTab === 'ST00060002') {
|
||||
const statusCodeResponse = await statisticsApi.CheckStatusCode({
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
|
||||
sewerBzmnCd: toInputSearchRegion(sidoListRef.current, lastSearchedValues).sewerBzmnCd,
|
||||
statsCd: 'ST00060001',
|
||||
statsYr: lastSearchedValues.year ?? ''
|
||||
});
|
||||
@@ -523,19 +562,36 @@ const PublicSewageFacilityInp = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const saveList: PublicSewageMainFacilityMappingSaveItem[] = rightRows
|
||||
.filter((r) => isCheckedRow(r))
|
||||
.map((r) => ({
|
||||
fcltSn: String(r.fcltSn ?? ''),
|
||||
admdstCd: String(r.admdstCd ?? ''),
|
||||
admdstNm: String(r.admdstNm ?? ''),
|
||||
swtrmtFcltCd: String(r.swtrmtFcltCd ?? ''),
|
||||
swtrmtFcltNm: String(r.swtrmtFcltNm ?? ''),
|
||||
isMainYn: String(r.isMainYn ?? ''),
|
||||
}));
|
||||
const checkedRows = rightRows.filter((r) => isCheckedRow(r));
|
||||
const exclusiveSelf = hasCheckedSelfMapping(rightRows, selectedMainFacilitySn);
|
||||
const saveList: PublicSewageMainFacilityMappingSaveItem[] = (
|
||||
exclusiveSelf
|
||||
? checkedRows.filter((r) => isSelfMappingTarget(r, selectedMainFacilitySn))
|
||||
: checkedRows
|
||||
).map((r) => ({
|
||||
fcltSn: nullableTrimmedString(r.fcltSn),
|
||||
admdstCd: nullableTrimmedString(r.admdstCd),
|
||||
admdstNm: nullableTrimmedString(r.admdstNm),
|
||||
swtrmtFcltCd: nullableTrimmedString(r.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(r.swtrmtFcltNm),
|
||||
isMainYn: nullableTrimmedString(r.isMainYn),
|
||||
}));
|
||||
|
||||
const exclusiveDroppedSns = exclusiveSelf
|
||||
? new Set(
|
||||
checkedRows
|
||||
.filter((r) => !isSelfMappingTarget(r, selectedMainFacilitySn))
|
||||
.map((r) => String(r.fcltSn ?? ''))
|
||||
.filter((sn) => sn !== ''),
|
||||
)
|
||||
: new Set<string>();
|
||||
|
||||
const deleteList = rightRows
|
||||
.filter((r) => String(r.mappedYn ?? '') === 'Y' && !isCheckedRow(r))
|
||||
.filter((r) => {
|
||||
const sn = String(r.fcltSn ?? '');
|
||||
if (String(r.mappedYn ?? '') === 'Y' && !isCheckedRow(r)) return true;
|
||||
return exclusiveDroppedSns.has(sn) && String(r.mappedYn ?? '') === 'Y';
|
||||
})
|
||||
.map((r) => String(r.fcltSn ?? ''))
|
||||
.filter((sn) => sn !== '');
|
||||
|
||||
@@ -549,7 +605,7 @@ const PublicSewageFacilityInp = () => {
|
||||
|
||||
mainFacilityMappingSaveDraftRef.current = {
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
|
||||
sewerBzmnCd: toInputSearchRegion(sidoListRef.current, lastSearchedValues).sewerBzmnCd,
|
||||
admdstCd: String(selectedMain.admdstCd ?? ''),
|
||||
admdstNm: String(selectedMain.admdstNm ?? ''),
|
||||
mainFcltSn: String(selectedMain.fcltSn ?? ''),
|
||||
@@ -580,7 +636,7 @@ const PublicSewageFacilityInp = () => {
|
||||
|
||||
if (activeTab === 'ST00060001') {
|
||||
const noApplicable = getNoApplicableForRequest();
|
||||
const treatAsNoApplicable = noApplicable || isNotApplicable;
|
||||
const treatAsNoApplicable = noApplicable;
|
||||
const currentData =
|
||||
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
|
||||
const prxyCtrtCnError = validatePrxyCtrtCnInRows(currentData);
|
||||
@@ -589,7 +645,7 @@ const PublicSewageFacilityInp = () => {
|
||||
return;
|
||||
}
|
||||
const saveList = treatAsNoApplicable ? [] : buildFacilitySaveList(currentData);
|
||||
if (!treatAsNoApplicable && saveList.length === 0) {
|
||||
if (!treatAsNoApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(activeTab) ?? lastSearchedValues)) {
|
||||
alert('해당사항 없음을 체크해주세요.');
|
||||
return;
|
||||
}
|
||||
@@ -600,9 +656,8 @@ const PublicSewageFacilityInp = () => {
|
||||
const param: PublicSewageFacilitySaveRequest = {
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: 'superAdmin' } : {}),
|
||||
noApplicable,
|
||||
saveList,
|
||||
@@ -644,35 +699,28 @@ const PublicSewageFacilityInp = () => {
|
||||
const firstTabKey = PublicSewageFactInpColumnList[0]?.tabKey ?? 'ST00060001';
|
||||
const searchParamRef = useRef<PublicSewageFacilityRequestParams | null>(null);
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.PUBLIC_SEWAGE_FACILITY) return;
|
||||
const tabKey = activeTab;
|
||||
if (!tabKey) return;
|
||||
if (Object.prototype.hasOwnProperty.call(columnGuidesByTab, tabKey)) return;
|
||||
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
PUBLIC_SEWAGE_FACILITY_COLUMN_GUIDE_TAB_KEYS.map(async (tabKey) => {
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(tabKey);
|
||||
if (res.status !== 'success' || !Array.isArray(res.data)) {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [tabKey, next] as const;
|
||||
} catch {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
const next = await fetchColumnGuidesRecord(tabKey);
|
||||
if (cancelled) return;
|
||||
setColumnGuidesByTab(Object.fromEntries(entries));
|
||||
setColumnGuidesByTab((prev) =>
|
||||
Object.prototype.hasOwnProperty.call(prev, tabKey)
|
||||
? prev
|
||||
: { ...prev, [tabKey]: next },
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [activeTabId, activeTab, columnGuidesByTab]);
|
||||
|
||||
const commitToolbarSnapshotForLoadedTab = (
|
||||
tabKey: string,
|
||||
@@ -695,12 +743,18 @@ const PublicSewageFacilityInp = () => {
|
||||
);
|
||||
return next;
|
||||
});
|
||||
if (searchContext) {
|
||||
setSearchContextByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, searchContext);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const buildPublicSewageFacilityParams = (values: SearchFormValues): PublicSewageFacilityRequestParams => ({
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
fcltNm: values.fctName,
|
||||
statsYr: values.year,
|
||||
});
|
||||
@@ -730,7 +784,11 @@ const PublicSewageFacilityInp = () => {
|
||||
const statusInfo = response.data.statusInfo ?? null;
|
||||
const statsCd = response.data.commonInfo.statsCd || tabKey;
|
||||
const commonInfo = response.data.commonInfo;
|
||||
const list = response.data.publicSewageFacilityList ?? [];
|
||||
const list = filterBySelectedAdmdstCd(
|
||||
response.data.publicSewageFacilityList ?? [],
|
||||
sidoListRef.current,
|
||||
searchContext,
|
||||
);
|
||||
const convertedData = list.map((item) => ({
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
...item,
|
||||
@@ -759,6 +817,9 @@ const PublicSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('ST00060001', gridRows);
|
||||
@@ -806,6 +867,9 @@ const PublicSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, response.data.commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, response.data.commonInfo),
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('MainFacility', leftGridRows);
|
||||
@@ -837,9 +901,8 @@ const PublicSewageFacilityInp = () => {
|
||||
const request = {
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userUuid: userInfo.userUnqId,
|
||||
};
|
||||
|
||||
const response =
|
||||
@@ -886,7 +949,7 @@ const PublicSewageFacilityInp = () => {
|
||||
admdstNm: draft.admdstNm,
|
||||
mainFcltSn: draft.mainFcltSn,
|
||||
mainFcltNm: draft.mainFcltNm,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: draft.saveList,
|
||||
deleteList: draft.deleteList,
|
||||
};
|
||||
@@ -968,7 +1031,9 @@ const PublicSewageFacilityInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
@@ -1031,7 +1096,9 @@ const PublicSewageFacilityInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
|
||||
setInputPeriodEnterable(true);
|
||||
periodEnterableRef.current = true;
|
||||
@@ -1058,11 +1125,10 @@ const PublicSewageFacilityInp = () => {
|
||||
const userInfo = await getUserInfo();
|
||||
const statsCdForCancel = tabStatsCdMap.get(activeTab) ?? activeTab;
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues?.bsnmCd ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues?.sido ?? '',
|
||||
sggNm: lastSearchedValues?.gugun === 'ALL' ? '' : lastSearchedValues?.gugun ?? '',
|
||||
statsYr: lastSearchedValues?.year ?? '',
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForCancel,
|
||||
};
|
||||
|
||||
@@ -1089,13 +1155,16 @@ const PublicSewageFacilityInp = () => {
|
||||
const getGridColumnInfo = (
|
||||
tab: ColumnInfoList,
|
||||
guideTabKey?: string,
|
||||
canEdit = true,
|
||||
): ColumnInfo => ({
|
||||
columns: (tab.columns ?? []).map((col) => {
|
||||
const guide = columnGuidesByTab[guideTabKey ?? tab.tabKey]?.[col.fieldName]?.trim();
|
||||
return {
|
||||
const withGuide = {
|
||||
...col,
|
||||
tooltip: guide && guide.length > 0 ? guide : undefined,
|
||||
};
|
||||
if (canEdit) return withGuide;
|
||||
return { ...withGuide, editable: false as const };
|
||||
}),
|
||||
columnLayout: tab.columnLayout ?? [],
|
||||
});
|
||||
@@ -1115,7 +1184,9 @@ const PublicSewageFacilityInp = () => {
|
||||
const param: PublicSewageMappingTargetsRequestParams = {
|
||||
sewerBzmnCd: searchParamRef.current.sewerBzmnCd ?? '',
|
||||
ctpvNm: searchParamRef.current.ctpvNm ?? '',
|
||||
sggNm: searchParamRef.current.sggNm ?? '',
|
||||
...(searchParamRef.current.sggNm != null
|
||||
? { sggNm: searchParamRef.current.sggNm }
|
||||
: {}),
|
||||
statsYr: searchParamRef.current.statsYr ?? '',
|
||||
fcltSn,
|
||||
};
|
||||
@@ -1131,13 +1202,18 @@ const PublicSewageFacilityInp = () => {
|
||||
if (rawList.length === 0) {
|
||||
alert('해당 시설은 중심처리시설의 위탁시설로 연결되어있습니다.');
|
||||
}
|
||||
const mappedTargetRows = rawList.map((item) => ({
|
||||
...item,
|
||||
__checked: item.mappedYn === 'Y' ? 'Y' : '',
|
||||
}));
|
||||
const mappedTargetRows = applyExclusiveSelfMappingChecks(
|
||||
toGridRows(
|
||||
rawList.map((item) => ({
|
||||
...item,
|
||||
__checked: item.mappedYn === 'Y' ? 'Y' : '',
|
||||
})),
|
||||
),
|
||||
fcltSn,
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('MainFacilityTarget', toGridRows(mappedTargetRows));
|
||||
next.set('MainFacilityTarget', mappedTargetRows);
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
@@ -1152,8 +1228,9 @@ const PublicSewageFacilityInp = () => {
|
||||
return String(row.fcltSn ?? '') === selectedMainFacilitySn ? 'rg-cell-selected-row' : undefined;
|
||||
};
|
||||
|
||||
const searchContextForToolbar = lastSearchedValues ?? searchValues;
|
||||
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
||||
const gugunOkOrNotDoList = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
||||
const activeTabToolbar = computeTabToolbarSnapshot(
|
||||
activeTabStatus,
|
||||
@@ -1173,7 +1250,7 @@ const PublicSewageFacilityInp = () => {
|
||||
isMainFacilityTab &&
|
||||
tabRowDataExists('ST00060002') &&
|
||||
gugunOkOrNotDoList &&
|
||||
(isAdminInfo || inputPeriodEnterable);
|
||||
inputPeriodEnterable;
|
||||
const showSaveInToolbar =
|
||||
toolbarGroupAllowed && (baseShowSaveInToolbar || showMainFacilitySaveInToolbar);
|
||||
const showEnterableToolbarButtons =
|
||||
@@ -1198,9 +1275,8 @@ const PublicSewageFacilityInp = () => {
|
||||
const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab;
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === 'ALL' ? '' : (ctx.gugun ?? ''),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -1221,6 +1297,7 @@ const PublicSewageFacilityInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName={true}
|
||||
@@ -1241,12 +1318,12 @@ const PublicSewageFacilityInp = () => {
|
||||
)}
|
||||
{showEnterableToolbarButtons && !isMainFacilityTab && (
|
||||
<>
|
||||
{showIsNotApplicable && <NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />}
|
||||
{showIsNotApplicable && submitOk && <NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />}
|
||||
<UpdateOperateDataButton updateOperateDataButtonFunc={updateOperateDataButtonFunc} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{!isMainFacilityTab && activeTabStatus?.laeSbmsnSttsCd === '2' && (
|
||||
{!isMainFacilityTab && activeTabStatus?.lclgvSbmsnSttsCd === '2' && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
@@ -1270,14 +1347,28 @@ const PublicSewageFacilityInp = () => {
|
||||
</TabsList>
|
||||
|
||||
{PublicSewageFactInpColumnList.map((tab) => {
|
||||
const tabStatus = tabStatusInfoMap.get(tab.tabKey) ?? null;
|
||||
const tabEnterableForGrid =
|
||||
(computeTabEnterable(tabStatus, inputPeriodEnterable) ||
|
||||
isAdminInfo) &&
|
||||
inputPeriodEnterable;
|
||||
|
||||
if (tab.isSplitGrid && tab.left && tab.right) {
|
||||
const leftKey = tab.left.tabKey ?? `${tab.tabKey}_left`;
|
||||
const rightKey = tab.right.tabKey ?? `${tab.tabKey}_right`;
|
||||
return (
|
||||
<TabsContent key={tab.tabKey} value={tab.tabKey}>
|
||||
<SplitDataGrid
|
||||
leftColumnInfo={getGridColumnInfo(tab.left, tab.tabKey)}
|
||||
rightColumnInfo={getGridColumnInfo(tab.right, tab.tabKey)}
|
||||
leftColumnInfo={getGridColumnInfo(
|
||||
tab.left,
|
||||
tab.tabKey,
|
||||
tabEnterableForGrid,
|
||||
)}
|
||||
rightColumnInfo={getGridColumnInfo(
|
||||
tab.right,
|
||||
tab.tabKey,
|
||||
tabEnterableForGrid,
|
||||
)}
|
||||
leftFixedColumns={tab.left.fixedColumns}
|
||||
rightFixedColumns={tab.right.fixedColumns}
|
||||
showHeaderCheckbox={true}
|
||||
@@ -1303,11 +1394,25 @@ const PublicSewageFacilityInp = () => {
|
||||
const next = new Map(prev);
|
||||
const rows = [...(next.get('MainFacilityTarget') ?? [])];
|
||||
if (!rows[itemIndex]) return prev;
|
||||
const clicked = rows[itemIndex];
|
||||
if (
|
||||
checked &&
|
||||
hasCheckedSelfMapping(rows, selectedMainFacilitySn) &&
|
||||
!isSelfMappingTarget(clicked, selectedMainFacilitySn)
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
rows[itemIndex] = {
|
||||
...rows[itemIndex],
|
||||
...clicked,
|
||||
__checked: checked ? 'Y' : '',
|
||||
};
|
||||
next.set('MainFacilityTarget', rows);
|
||||
next.set(
|
||||
'MainFacilityTarget',
|
||||
applyExclusiveSelfMappingChecks(
|
||||
rows,
|
||||
selectedMainFacilitySn,
|
||||
),
|
||||
);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
@@ -1318,16 +1423,59 @@ const PublicSewageFacilityInp = () => {
|
||||
? (checked) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const rows = [...(next.get('MainFacilityTarget') ?? [])].map((r) => ({
|
||||
...r,
|
||||
__checked: checked ? 'Y' : '',
|
||||
}));
|
||||
next.set('MainFacilityTarget', rows);
|
||||
const rows = [...(next.get('MainFacilityTarget') ?? [])];
|
||||
if (
|
||||
checked &&
|
||||
hasCheckedSelfMapping(rows, selectedMainFacilitySn)
|
||||
) {
|
||||
next.set(
|
||||
'MainFacilityTarget',
|
||||
applyExclusiveSelfMappingChecks(
|
||||
rows,
|
||||
selectedMainFacilitySn,
|
||||
),
|
||||
);
|
||||
return next;
|
||||
}
|
||||
next.set(
|
||||
'MainFacilityTarget',
|
||||
rows.map((r) => ({
|
||||
...r,
|
||||
__checked:
|
||||
checked &&
|
||||
!isSelfMappingTarget(
|
||||
r,
|
||||
selectedMainFacilitySn,
|
||||
)
|
||||
? 'Y'
|
||||
: '',
|
||||
})),
|
||||
);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
rightCheckableCallback={
|
||||
tab.tabKey === 'ST00060002'
|
||||
? (row) => {
|
||||
const rows =
|
||||
rowDataMap.get('MainFacilityTarget') ?? [];
|
||||
if (
|
||||
!hasCheckedSelfMapping(
|
||||
rows,
|
||||
selectedMainFacilitySn,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return isSelfMappingTarget(
|
||||
row,
|
||||
selectedMainFacilitySn,
|
||||
);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
setLeftRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
@@ -1357,11 +1505,10 @@ const PublicSewageFacilityInp = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const gridColumnInfo = getGridColumnInfo(tab);
|
||||
const tabStatus = tabStatusInfoMap.get(tab.tabKey) ?? null;
|
||||
const tabEnterableForGrid = computeTabEnterable(
|
||||
tabStatus,
|
||||
inputPeriodEnterable,
|
||||
const gridColumnInfo = getGridColumnInfo(
|
||||
tab,
|
||||
undefined,
|
||||
tabEnterableForGrid,
|
||||
);
|
||||
const excelToolbarEnabledForTab =
|
||||
tabRowDataExists(tab.tabKey) &&
|
||||
@@ -1371,7 +1518,7 @@ const PublicSewageFacilityInp = () => {
|
||||
return (
|
||||
<TabsContent key={tab.tabKey} value={tab.tabKey}>
|
||||
<DataGrid
|
||||
key={tab.tabKey}
|
||||
key={`${tab.tabKey}-${tabEnterableForGrid ? 'enterable' : 'readonly'}`}
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={tab.rowStartNum}
|
||||
excelExportFileSuffix={tab.tabLabel || tab.tabKey}
|
||||
@@ -1400,14 +1547,29 @@ const PublicSewageFacilityInp = () => {
|
||||
showHeaderCheckbox={false}
|
||||
enableSort={false}
|
||||
cellStyleCallback={({ row, fieldName }) => {
|
||||
const lockPrxyCtrtCn =
|
||||
fieldName === 'prxyCtrtCn' && isMainFacilityRow(row);
|
||||
const verification = (row as GridRowWithVerification).verificationResult;
|
||||
if (!verification) return;
|
||||
const errors = verification[fieldName];
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
const hasImr = errors.some((e) => e.vrfcCd === "IMR" || e.vrfcCd === "HB");
|
||||
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
|
||||
let verifyStyle: string | undefined;
|
||||
if (verification) {
|
||||
const errors = verification[fieldName];
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
const hasImr = errors.some((e) => e.vrfcCd === "IMR" || e.vrfcCd === "HB");
|
||||
verifyStyle = hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
|
||||
}
|
||||
}
|
||||
if (lockPrxyCtrtCn) {
|
||||
return {
|
||||
editable: false,
|
||||
readOnly: true,
|
||||
styleName: [verifyStyle, 'rg-cell-readonly'].filter(Boolean).join(' ') || 'rg-cell-readonly',
|
||||
};
|
||||
}
|
||||
return verifyStyle;
|
||||
}}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tab.tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={({ row, fieldName }) => {
|
||||
const verification = (row as GridRowWithVerification).verificationResult;
|
||||
if (!verification) return;
|
||||
@@ -1416,9 +1578,9 @@ const PublicSewageFacilityInp = () => {
|
||||
return errors
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR" ? "IMR검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "HB검증 - ");
|
||||
const comp = e.compVlCn ?? "";
|
||||
const now = e.nowVlCn ?? "";
|
||||
e.vrfcCd === "IMR" ? "시계열 검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - ");
|
||||
const comp = e.cmprCn ?? "";
|
||||
const now = e.nowCn ?? "";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${comp} / 현재 입력값: ${now})`;
|
||||
})
|
||||
|
||||
@@ -1,20 +1,56 @@
|
||||
import {
|
||||
type RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
|
||||
import { flushSync } from "react-dom";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { ValueType } from "realgrid";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { statisticsInputPumpingStationApi } from "@/entities/statistics/api/input/statistics-input-pumping-station-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
PUMPING_STATION_STATS_CD,
|
||||
type PumpingStationItem,
|
||||
type PumpingStationRequestParams,
|
||||
type PumpingStationSaveItem,
|
||||
type PumpingStationVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-pumping-station-interface";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type {
|
||||
ApiResponse,
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sgg,
|
||||
Sido,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
resolveNoApplicableForRequest,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
import AddArticleButton from "@/components/button/AddArticleButton";
|
||||
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
||||
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
|
||||
@@ -24,49 +60,24 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import { PumpingStationInpColumn } from "@/components/util/column/input/PumpingStationInpColumn";
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
import type {
|
||||
CellStyleCallbackParams,
|
||||
CellTooltipCallbackParams,
|
||||
ColumnConfig,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { PumpingStationInpColumn } from "@/components/util/column/input/PumpingStationInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputPumpingStationApi } from "@/entities/statistics/api/input/statistics-input-pumping-station-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import {
|
||||
PUMPING_STATION_STATS_CD,
|
||||
type PumpingStationItem,
|
||||
type PumpingStationRequestParams,
|
||||
type PumpingStationSaveItem,
|
||||
type PumpingStationVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-pumping-station-interface";
|
||||
import type {
|
||||
ApiResponse,
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
Sgg,
|
||||
SewerageStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: PumpingStationVerificationResult;
|
||||
@@ -82,8 +93,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -91,10 +102,22 @@ const computeTabEnterable = (
|
||||
return periodEnterable;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || "")
|
||||
? Boolean(values?.gugun && values.gugun !== "ALL")
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildNoApplicableForRequest(
|
||||
values: SearchFormValues | null,
|
||||
checked: boolean,
|
||||
isSaveListEmpty: boolean,
|
||||
): boolean {
|
||||
return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty);
|
||||
}
|
||||
|
||||
function computeTabToolbarSnapshot(
|
||||
@@ -107,8 +130,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -128,9 +151,8 @@ function buildPumpingStationSearchParam(
|
||||
const fcltNm = values.fctName?.trim();
|
||||
return {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
...(fcltNm ? { fcltNm } : {}),
|
||||
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
||||
};
|
||||
@@ -190,13 +212,6 @@ function mapPumpingStationListToGridRows(
|
||||
});
|
||||
}
|
||||
|
||||
function nullableTrimmedString(
|
||||
value: string | number | null | undefined,
|
||||
): string | null {
|
||||
const s = String(value ?? "").trim();
|
||||
return s === "" ? null : s;
|
||||
}
|
||||
|
||||
function toGridNumber(value: string | number | null | undefined): number {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
@@ -205,9 +220,9 @@ function toGridNumber(value: string | number | null | undefined): number {
|
||||
function resolvePumpingStationAdmdst(
|
||||
row: Record<string, string | number>,
|
||||
sggOptions: Sgg[],
|
||||
): { admdstCd: string; admdstNm: string } {
|
||||
const existingCd = String(row.admdstCd ?? "").trim();
|
||||
const existingNm = String(row.admdstNm ?? "").trim();
|
||||
): { admdstCd: string | null; admdstNm: string | null } {
|
||||
const existingCd = nullableTrimmedString(row.admdstCd);
|
||||
const existingNm = nullableTrimmedString(row.admdstNm);
|
||||
if (existingCd && existingNm) {
|
||||
return { admdstCd: existingCd, admdstNm: existingNm };
|
||||
}
|
||||
@@ -216,7 +231,10 @@ function resolvePumpingStationAdmdst(
|
||||
if (matched) {
|
||||
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
|
||||
}
|
||||
return { admdstCd: existingCd, admdstNm: existingNm || sggNm };
|
||||
return {
|
||||
admdstCd: existingCd,
|
||||
admdstNm: existingNm ?? nullableTrimmedString(sggNm),
|
||||
};
|
||||
}
|
||||
|
||||
/** 그리드 행 → ST00050001 저장/삭제 API 항목 */
|
||||
@@ -225,44 +243,43 @@ function buildPumpingStationSaveItem(
|
||||
sggOptions: Sgg[] = [],
|
||||
): PumpingStationSaveItem {
|
||||
const { admdstCd, admdstNm } = resolvePumpingStationAdmdst(row, sggOptions);
|
||||
const fcltSn = String(row.fcltSn ?? "").trim();
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
fcltSn: fcltSn,
|
||||
swtrmtFcltNm: String(row.swtrmtFcltNm ?? ""),
|
||||
fcltNm: String(row.fcltNm ?? ""),
|
||||
pumpSeNm: String(row.pumpSeNm ?? ""),
|
||||
fcltPstnCn: String(row.fcltPstnCn ?? ""),
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
|
||||
fcltNm: nullableTrimmedString(row.fcltNm),
|
||||
pumpSeNm: nullableTrimmedString(row.pumpSeNm),
|
||||
fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
|
||||
oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd),
|
||||
planMltvArea: toGridNumber(row.planMltvArea),
|
||||
sndyArea: toGridNumber(row.sndyArea),
|
||||
planDrnar: toGridNumber(row.planDrnar),
|
||||
icodwArea: toGridNumber(row.icodwArea),
|
||||
rainArea: toGridNumber(row.rainArea),
|
||||
drtwPumpN1DmtrArea: toGridNumber(row.drtwPumpN1DmtrArea),
|
||||
drtwPumpN1Dmtr: toGridNumber(row.drtwPumpN1Dmtr),
|
||||
drtwPumpN1Ppqty: toGridNumber(row.drtwPumpN1Ppqty),
|
||||
drtwPumpN1Cnt: toGridNumber(row.drtwPumpN1Cnt),
|
||||
drtwPumpN1RsrvCnt: toGridNumber(row.drtwPumpN1RsrvCnt),
|
||||
drtwPumpN2DmtrArea: toGridNumber(row.drtwPumpN2DmtrArea),
|
||||
drtwPumpN2Dmtr: toGridNumber(row.drtwPumpN2Dmtr),
|
||||
drtwPumpN2Ppqty: toGridNumber(row.drtwPumpN2Ppqty),
|
||||
drtwPumpN2Cnt: toGridNumber(row.drtwPumpN2Cnt),
|
||||
drtwPumpN2RsrvCnt: toGridNumber(row.drtwPumpN2RsrvCnt),
|
||||
drtwPumpN3DmtrArea: toGridNumber(row.drtwPumpN3DmtrArea),
|
||||
drtwPumpN3Dmtr: toGridNumber(row.drtwPumpN3Dmtr),
|
||||
drtwPumpN3Ppqty: toGridNumber(row.drtwPumpN3Ppqty),
|
||||
drtwPumpN3Cnt: toGridNumber(row.drtwPumpN3Cnt),
|
||||
drtwPumpN3RsrvCnt: toGridNumber(row.drtwPumpN3RsrvCnt),
|
||||
exlnPumpN1DmtrArea: toGridNumber(row.exlnPumpN1DmtrArea),
|
||||
exlnPumpN1Dmtr: toGridNumber(row.exlnPumpN1Dmtr),
|
||||
exlnPumpN1Ppqty: toGridNumber(row.exlnPumpN1Ppqty),
|
||||
exlnPumpN1Cnt: toGridNumber(row.exlnPumpN1Cnt),
|
||||
exlnPumpN1RsrvCnt: toGridNumber(row.exlnPumpN1RsrvCnt),
|
||||
exlnPumpN2DmtrArea: toGridNumber(row.exlnPumpN2DmtrArea),
|
||||
exlnPumpN2Dmtr: toGridNumber(row.exlnPumpN2Dmtr),
|
||||
exlnPumpN2Ppqty: toGridNumber(row.exlnPumpN2Ppqty),
|
||||
exlnPumpN2Cnt: toGridNumber(row.exlnPumpN2Cnt),
|
||||
exlnPumpN2RsrvCnt: toGridNumber(row.exlnPumpN2RsrvCnt),
|
||||
exlnPumpN3DmtrArea: toGridNumber(row.exlnPumpN3DmtrArea),
|
||||
exlnPumpN3Dmtr: toGridNumber(row.exlnPumpN3Dmtr),
|
||||
exlnPumpN3Ppqty: toGridNumber(row.exlnPumpN3Ppqty),
|
||||
exlnPumpN3Cnt: toGridNumber(row.exlnPumpN3Cnt),
|
||||
exlnPumpN3RsrvCnt: toGridNumber(row.exlnPumpN3RsrvCnt),
|
||||
anlElpwrUseQnt: toGridNumber(row.anlElpwrUseQnt),
|
||||
anlElpwrUsqty: toGridNumber(row.anlElpwrUsqty),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -285,11 +302,11 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
function createEmptyPumpingStationRow(
|
||||
@@ -311,6 +328,8 @@ const PUMPING_STATION_FACILITY_NAME_GUIDE_MESSAGE =
|
||||
|
||||
// 하수도 통계 관리 > 05. 펌프장 (SewageReservoirPumpInp 단일 탭 패턴)
|
||||
const PumpingStationInp = () => {
|
||||
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
@@ -333,6 +352,8 @@ const PumpingStationInp = () => {
|
||||
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
|
||||
const [statsCd, setStatsCd] = useState("");
|
||||
const [isVerificationFailed, setIsVerificationFailed] = useState(false);
|
||||
const [footerVerificationResult, setFooterVerificationResult] =
|
||||
useState<VerificationResultLikeMap | null>(null);
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
||||
@@ -353,7 +374,10 @@ const PumpingStationInp = () => {
|
||||
const sggOptionsSidoRef = useRef<string | null>(null);
|
||||
const [searchLoaded, setSearchLoaded] = useState(false);
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.PUMPING_STATION) return;
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
try {
|
||||
@@ -377,7 +401,7 @@ const PumpingStationInp = () => {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [activeTabId]);
|
||||
|
||||
const loadSggOptions = useCallback(async (sido: string): Promise<Sgg[]> => {
|
||||
if (!sido || sido === "ALL") {
|
||||
@@ -397,7 +421,7 @@ const PumpingStationInp = () => {
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const enterable =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = PumpingStationInpColumn.columns ?? [];
|
||||
const base = enterable
|
||||
? rawColumns
|
||||
@@ -425,7 +449,7 @@ const PumpingStationInp = () => {
|
||||
}, [statusInfo, inputPeriodEnterable, isAdminInfo, sggOptions, columnGuidesByEngCol]);
|
||||
|
||||
const canEditGrid =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
|
||||
const getCurrentRows = (): Record<string, string | number>[] =>
|
||||
gridGetRowsRef.current?.() ?? rowData;
|
||||
@@ -457,7 +481,11 @@ const PumpingStationInp = () => {
|
||||
|
||||
const { commonInfo, statusInfo: nextStatusInfo, pumpingStationList } =
|
||||
response.data;
|
||||
const list = pumpingStationList ?? [];
|
||||
const list = filterBySelectedAdmdstCd(
|
||||
pumpingStationList ?? [],
|
||||
sidoListRef.current,
|
||||
values,
|
||||
);
|
||||
const gridRows = mapPumpingStationListToGridRows(list, commonInfo.ctpvNm);
|
||||
|
||||
setPumpingStationDeleteList([]);
|
||||
@@ -465,6 +493,7 @@ const PumpingStationInp = () => {
|
||||
setStatusInfo(nextStatusInfo);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
setFooterVerificationResult(commonInfo.verificationResult ?? null);
|
||||
if (overrides?.mode !== "PRE") {
|
||||
setIsNotApplicable(gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
@@ -524,6 +553,7 @@ const PumpingStationInp = () => {
|
||||
setStatusInfo(null);
|
||||
setStatsCd("");
|
||||
setIsVerificationFailed(false);
|
||||
setFooterVerificationResult(null);
|
||||
setIsNotApplicable(false);
|
||||
setInputPeriodEnterable(true);
|
||||
setSggOptions([]);
|
||||
@@ -573,14 +603,10 @@ const PumpingStationInp = () => {
|
||||
|
||||
const userInfo = await getUserInfo();
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCd || PUMPING_STATION_STATS_CD,
|
||||
};
|
||||
|
||||
@@ -613,9 +639,9 @@ const PumpingStationInp = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const noApplicable = isNotApplicable;
|
||||
const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
|
||||
const currentData = getCurrentRows();
|
||||
if (!noApplicable && currentData.length === 0) {
|
||||
if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -642,7 +668,7 @@ const PumpingStationInp = () => {
|
||||
if (!(await confirm("저장 하시겠습니까?"))) return;
|
||||
|
||||
commitGrid();
|
||||
const noApplicable = isNotApplicable;
|
||||
const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
|
||||
const currentData = getCurrentRows();
|
||||
const saveList = noApplicable
|
||||
? []
|
||||
@@ -653,7 +679,7 @@ const PumpingStationInp = () => {
|
||||
.filter((row) => String(row.fcltSn ?? "").trim() !== "")
|
||||
.map((row) => buildPumpingStationSaveItem(row, sggOptions));
|
||||
|
||||
if (!noApplicable && saveList.length === 0) {
|
||||
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -664,12 +690,8 @@ const PumpingStationInp = () => {
|
||||
const response = await statisticsInputPumpingStationApi.savePumpingStationList({
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
noApplicable,
|
||||
saveList,
|
||||
@@ -702,7 +724,7 @@ const PumpingStationInp = () => {
|
||||
}
|
||||
|
||||
commitGrid();
|
||||
const noApplicable = isNotApplicable;
|
||||
const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
|
||||
const currentData = getCurrentRows();
|
||||
const submitList = noApplicable
|
||||
? []
|
||||
@@ -711,19 +733,15 @@ const PumpingStationInp = () => {
|
||||
.filter((row) => String(row.fcltSn ?? "").trim() !== "")
|
||||
.map((row) => buildPumpingStationSaveItem(row, sggOptions));
|
||||
|
||||
if (!noApplicable && submitList.length === 0) {
|
||||
if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
const statsCdForSubmit = statsCd || PUMPING_STATION_STATS_CD;
|
||||
const verificationQuery = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
statsCd: statsCdForSubmit,
|
||||
};
|
||||
@@ -740,20 +758,16 @@ const PumpingStationInp = () => {
|
||||
const response = await statisticsInputPumpingStationApi.submitPumpingStationList({
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userUuid: userInfo.userUnqId,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
noApplicable,
|
||||
submitList,
|
||||
@@ -770,15 +784,16 @@ const PumpingStationInp = () => {
|
||||
|
||||
if (finalStatus === "5") {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel("IMR 검증 진행 중...");
|
||||
setGlobalProgressLabel("시계열 검증 진행 중...");
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "",
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForSubmit,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
statusInfo?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
statusInfo?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -925,19 +940,19 @@ const PumpingStationInp = () => {
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR"
|
||||
? "IMR검증 - "
|
||||
? "시계열 검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "HB검증 -";
|
||||
: "모집단 검증 -";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
};
|
||||
|
||||
const searchContextForToolbar = lastSearchedValues ?? searchValues;
|
||||
const searchContextForToolbar = lastSearchedValues;
|
||||
const toolbarSnapshot = computeTabToolbarSnapshot(
|
||||
statusInfo,
|
||||
rowData.length > 0,
|
||||
@@ -957,23 +972,23 @@ const PumpingStationInp = () => {
|
||||
hasSearched &&
|
||||
toolbarGroupAllowed &&
|
||||
(showSaveInToolbar || showEnterableToolbarButtons);
|
||||
const tabEnterableForGrid =
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
const showNotApplicable =
|
||||
searchLoaded &&
|
||||
hasSearched &&
|
||||
rowData.length === 0 &&
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable);
|
||||
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) &&
|
||||
submitOk;
|
||||
|
||||
const verificationQueryContext = (() => {
|
||||
const ctx = lastSearchedValues ?? searchValues;
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -989,6 +1004,7 @@ const PumpingStationInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName={true}
|
||||
@@ -1008,10 +1024,10 @@ const PumpingStationInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void getLastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{statusInfo?.laeSbmsnSttsCd === "2" && (
|
||||
{statusInfo?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
@@ -1056,6 +1072,7 @@ const PumpingStationInp = () => {
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={footerVerificationResult}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,55 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction } from "react";
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
import { ValueType } from "realgrid";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
import { type Dispatch, type SetStateAction,useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { flushSync } from "react-dom";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { ValueType } from "realgrid";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsInputSewageExcretaRelatedApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-related-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ExcretaRelatedIndustrySaveItem,
|
||||
PrivateSewageTreatRelatedIndustrySaveItem,
|
||||
PublicInstitutionExcretaIndustrySaveItem,
|
||||
PublicInstitutionSewerIndustrySaveItem,
|
||||
SewageExcretaRelatedRequestParams,
|
||||
SewageExcretaRelatedVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface";
|
||||
import {
|
||||
SEWAGE_EXCRETA_EXCRETA_RELATED_INDUSTRY_STATS_CD,
|
||||
SEWAGE_EXCRETA_PRIVATE_SEWAGE_TREAT_RELATED_INDUSTRY_STATS_CD,
|
||||
SEWAGE_EXCRETA_PUBLIC_INSTITUTION_EXCRETA_STATS_CD,
|
||||
SEWAGE_EXCRETA_PUBLIC_INSTITUTION_SEWER_STATS_CD,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type {
|
||||
ApiResponse,
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sgg,
|
||||
Sido,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
resolveNoApplicableForRequest,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
import AddArticleButton from "@/components/button/AddArticleButton";
|
||||
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
||||
@@ -16,7 +60,6 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -30,51 +73,25 @@ import type {
|
||||
ColumnInfo,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { ExcretaRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/ExcretaRelatedIndustryInpColumn";
|
||||
import { PrivateSewageTreatRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn";
|
||||
import { PublicInstitutionExcretaIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn";
|
||||
import { PublicInstitutionSewerIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { statisticsInputSewageExcretaRelatedApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-related-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ExcretaRelatedIndustrySaveItem,
|
||||
PrivateSewageTreatRelatedIndustrySaveItem,
|
||||
PublicInstitutionSewerIndustrySaveItem,
|
||||
SewageExcretaRelatedRequestParams,
|
||||
SewageExcretaRelatedVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import {
|
||||
SEWAGE_EXCRETA_EXCRETA_RELATED_INDUSTRY_STATS_CD,
|
||||
SEWAGE_EXCRETA_PRIVATE_SEWAGE_TREAT_RELATED_INDUSTRY_STATS_CD,
|
||||
SEWAGE_EXCRETA_PUBLIC_INSTITUTION_EXCRETA_STATS_CD,
|
||||
SEWAGE_EXCRETA_PUBLIC_INSTITUTION_SEWER_STATS_CD,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type {
|
||||
ApiResponse,
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sgg,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
const SewageExcretaRelatedInpTabList = [
|
||||
PublicInstitutionSewerIndustryInpColumn,
|
||||
PublicInstitutionExcretaIndustryInpColumn,
|
||||
ExcretaRelatedIndustryInpColumn,
|
||||
const SewageExcretaRelatedInpTabList = [
|
||||
PublicInstitutionSewerIndustryInpColumn,
|
||||
PublicInstitutionExcretaIndustryInpColumn,
|
||||
ExcretaRelatedIndustryInpColumn,
|
||||
PrivateSewageTreatRelatedIndustryInpColumn,
|
||||
] as const;
|
||||
|
||||
@@ -141,8 +158,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -150,10 +167,22 @@ const computeTabEnterable = (
|
||||
return periodEnterable;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || "")
|
||||
? Boolean(values?.gugun && values.gugun !== "ALL")
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildNoApplicableForRequest(
|
||||
values: SearchFormValues | null,
|
||||
checked: boolean,
|
||||
isSaveListEmpty: boolean,
|
||||
): boolean {
|
||||
return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty);
|
||||
}
|
||||
|
||||
function computeTabToolbarSnapshot(
|
||||
@@ -166,8 +195,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -210,10 +239,20 @@ function isCheckedRow(row: Record<string, string | number>): boolean {
|
||||
|
||||
type ArticleOgnzSnSet = Set<string>;
|
||||
|
||||
function extractOgnzSnSet(rows: Record<string, string | number>[]): ArticleOgnzSnSet {
|
||||
const PUBLIC_INSTITUTION_OGNZ_SN_FIELD = "statsOgnzSn" as const;
|
||||
|
||||
function getPublicInstitutionOgnzSn(
|
||||
row: Record<string, string | number>,
|
||||
): string {
|
||||
return String(row[PUBLIC_INSTITUTION_OGNZ_SN_FIELD] ?? "").trim();
|
||||
}
|
||||
|
||||
function extractOgnzSnSet(
|
||||
rows: Record<string, string | number>[],
|
||||
): ArticleOgnzSnSet {
|
||||
const set = new Set<string>();
|
||||
for (const row of rows) {
|
||||
const ognzSn = String(row.ognzSn ?? "").trim();
|
||||
const ognzSn = getPublicInstitutionOgnzSn(row);
|
||||
if (ognzSn) set.add(ognzSn);
|
||||
}
|
||||
return set;
|
||||
@@ -249,7 +288,7 @@ function isPersistedPublicInstitutionArticleRow(
|
||||
): boolean {
|
||||
if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) return false;
|
||||
if (isNewArticleRow(row)) return false;
|
||||
const ognzSn = String(row.ognzSn ?? "").trim();
|
||||
const ognzSn = getPublicInstitutionOgnzSn(row);
|
||||
if (!ognzSn) return false;
|
||||
if ((preYearRef.current.get(tabKey) ?? new Set()).has(ognzSn)) return false;
|
||||
return (persistedRef.current.get(tabKey) ?? new Set()).has(ognzSn);
|
||||
@@ -312,7 +351,7 @@ function buildSggNmColumn(
|
||||
function resolvePublicInstitutionAdmdst(
|
||||
row: Record<string, string | number>,
|
||||
sggOptions: Sgg[],
|
||||
): { admdstCd: string; admdstNm: string } {
|
||||
): { admdstCd: string | null; admdstNm: string | null } {
|
||||
const sggNm = String(row.sggNm ?? "").trim();
|
||||
if (sggNm) {
|
||||
const matched = sggOptions.find((s) => s.gugun === sggNm);
|
||||
@@ -320,9 +359,12 @@ function resolvePublicInstitutionAdmdst(
|
||||
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
|
||||
}
|
||||
}
|
||||
const existingCd = String(row.admdstCd ?? "").trim();
|
||||
const existingNm = String(row.admdstNm ?? "").trim();
|
||||
return { admdstCd: existingCd, admdstNm: existingNm || sggNm };
|
||||
const existingCd = nullableTrimmedString(row.admdstCd);
|
||||
const existingNm = nullableTrimmedString(row.admdstNm);
|
||||
return {
|
||||
admdstCd: existingCd,
|
||||
admdstNm: existingNm ?? nullableTrimmedString(sggNm),
|
||||
};
|
||||
}
|
||||
|
||||
function mergeGridRowsWithMapRows(
|
||||
@@ -362,13 +404,6 @@ function mergeGridRowsWithArticleMeta(
|
||||
});
|
||||
}
|
||||
|
||||
function nullableTrimmedString(
|
||||
value: string | number | null | undefined,
|
||||
): string | null {
|
||||
const s = String(value ?? "").trim();
|
||||
return s === "" ? null : s;
|
||||
}
|
||||
|
||||
function toGridNumber(value: string | number | null | undefined): number {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
@@ -378,18 +413,18 @@ function toGridNumber(value: string | number | null | undefined): number {
|
||||
function buildPublicInstitutionIndustrySaveItem(
|
||||
row: Record<string, string | number>,
|
||||
sggOptions: Sgg[],
|
||||
): PublicInstitutionSewerIndustrySaveItem {
|
||||
): PublicInstitutionSewerIndustrySaveItem | PublicInstitutionExcretaIndustrySaveItem {
|
||||
const { admdstCd, admdstNm } = resolvePublicInstitutionAdmdst(row, sggOptions);
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
ognzSn: String(row.ognzSn ?? "").trim(),
|
||||
ognzNm: String(row.ognzNm ?? "").trim(),
|
||||
statsOgnzSn: nullableTrimmedString(row.statsOgnzSn),
|
||||
ognzNm: nullableTrimmedString(row.ognzNm),
|
||||
pbadmsNope: toGridNumber(row.pbadmsNope),
|
||||
engrkCnt: toGridNumber(row.engrkCnt),
|
||||
mchnCnt: toGridNumber(row.mchnCnt),
|
||||
prvtmCnt: toGridNumber(row.prvtmCnt),
|
||||
chmengCnt: toGridNumber(row.chmengCnt),
|
||||
chemCnt: toGridNumber(row.chemCnt),
|
||||
envCnt: toGridNumber(row.envCnt),
|
||||
enjbEtcCnt: toGridNumber(row.enjbEtcCnt),
|
||||
etcCnt: toGridNumber(row.etcCnt),
|
||||
@@ -402,13 +437,13 @@ function buildExcretaRelatedIndustrySaveItem(
|
||||
row: Record<string, string | number>,
|
||||
): ExcretaRelatedIndustrySaveItem {
|
||||
return {
|
||||
admdstCd: String(row.admdstCd ?? "").trim(),
|
||||
admdstNm: String(row.admdstNm ?? "").trim(),
|
||||
admdstCd: nullableTrimmedString(row.admdstCd),
|
||||
admdstNm: nullableTrimmedString(row.admdstNm),
|
||||
bzentyCnt: toGridNumber(row.bzentyCnt),
|
||||
empNope: toGridNumber(row.empNope),
|
||||
n3TonUdrCnt: toGridNumber(row.n3TonUdrCnt),
|
||||
n4TonUdrCnt: toGridNumber(row.n4TonUdrCnt),
|
||||
n8TonUdrCnt: toGridNumber(row.n8TonUdrCnt),
|
||||
frlvlTonUdrCnt: toGridNumber(row.frlvlTonUdrCnt),
|
||||
sclvlTonUdrCnt: toGridNumber(row.sclvlTonUdrCnt),
|
||||
thlvlTonUdrCnt: toGridNumber(row.thlvlTonUdrCnt),
|
||||
etcCnt: toGridNumber(row.etcCnt),
|
||||
clctCnvncPrfmncQnt: toGridNumber(row.clctCnvncPrfmncQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
@@ -420,8 +455,8 @@ function buildPrivateSewageTreatRelatedIndustrySaveItem(
|
||||
row: Record<string, string | number>,
|
||||
): PrivateSewageTreatRelatedIndustrySaveItem {
|
||||
return {
|
||||
admdstCd: String(row.admdstCd ?? "").trim(),
|
||||
admdstNm: String(row.admdstNm ?? "").trim(),
|
||||
admdstCd: nullableTrimmedString(row.admdstCd),
|
||||
admdstNm: nullableTrimmedString(row.admdstNm),
|
||||
designMdsrCnt: toGridNumber(row.designMdsrCnt),
|
||||
designMdsrNope: toGridNumber(row.designMdsrNope),
|
||||
mfbizCnt: toGridNumber(row.mfbizCnt),
|
||||
@@ -521,12 +556,12 @@ function verificationCellTooltip({
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR"
|
||||
? "IMR검증 - "
|
||||
? "시계열 검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "HB검증 - ";
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -539,9 +574,8 @@ function buildSewageExcretaRelatedSearchParam(
|
||||
): SewageExcretaRelatedRequestParams {
|
||||
return {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
||||
};
|
||||
}
|
||||
@@ -549,8 +583,9 @@ function buildSewageExcretaRelatedSearchParam(
|
||||
function mapListToGridRows<T extends { verificationResult?: SewageExcretaRelatedVerificationResult }>(
|
||||
list: T[] | undefined,
|
||||
ctpvNm: string,
|
||||
values?: SearchFormValues | null,
|
||||
): GridRowWithVerification[] {
|
||||
return (list ?? []).map((item) => {
|
||||
return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
|
||||
const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification;
|
||||
if (item.verificationResult) {
|
||||
row.verificationResult = item.verificationResult;
|
||||
@@ -577,15 +612,17 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
// 하수도 통계 관리 > 12. 하수 및 분뇨관련산업
|
||||
const SewageExcretaRelatedInp = () => {
|
||||
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const isAdminInfo = useAdminStore((state) => state.isAdminInfo);
|
||||
@@ -602,7 +639,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
const [tabStatusInfoMap, setTabStatusInfoMap] = useState<
|
||||
Map<string, SewerageStatusInfo | null>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
const [columnGuidesByTab, setColumnGuidesByTab] = useState<
|
||||
@@ -612,6 +649,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(
|
||||
null,
|
||||
);
|
||||
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [hasSearched, setHasSearched] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
@@ -620,6 +660,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [isNotApplicableByTab, setIsNotApplicableByTab] = useState<Map<string, boolean>>(
|
||||
() => new Map(),
|
||||
);
|
||||
@@ -645,36 +688,29 @@ const SewageExcretaRelatedInp = () => {
|
||||
const sggOptionsSidoRef = useRef<string | null>(null);
|
||||
activeTabRef.current = activeTab;
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_RELATED) return;
|
||||
const tabKey = activeTab;
|
||||
if (!tabKey) return;
|
||||
if (Object.prototype.hasOwnProperty.call(columnGuidesByTab, tabKey)) return;
|
||||
const statsCd = tabKey;
|
||||
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
SewageExcretaRelatedInpTabList.map(async (tab) => {
|
||||
const tabKey = tab.tabKey;
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(tabKey);
|
||||
if (res.status !== "success" || !Array.isArray(res.data)) {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [tabKey, next] as const;
|
||||
} catch {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
const next = await fetchColumnGuidesRecord(statsCd);
|
||||
if (cancelled) return;
|
||||
setColumnGuidesByTab(Object.fromEntries(entries));
|
||||
setColumnGuidesByTab((prev) =>
|
||||
Object.prototype.hasOwnProperty.call(prev, tabKey)
|
||||
? prev
|
||||
: { ...prev, [tabKey]: next },
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [activeTabId, activeTab, columnGuidesByTab]);
|
||||
|
||||
const shouldShowNotApplicable = (
|
||||
tabKey: string,
|
||||
@@ -739,6 +775,13 @@ const SewageExcretaRelatedInp = () => {
|
||||
);
|
||||
return next;
|
||||
});
|
||||
if (searchContext) {
|
||||
setSearchContextByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, searchContext);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const markTabLoaded = (tabKey: string) => {
|
||||
@@ -854,6 +897,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
const gridRows = mapListToGridRows(
|
||||
publicInstitutionSewerIndustryList,
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
syncPublicInstitutionArticleTracking(
|
||||
tabKey,
|
||||
@@ -871,6 +915,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -897,6 +944,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
const gridRows = mapListToGridRows(
|
||||
publicInstitutionExcretaIndustryList,
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
syncPublicInstitutionArticleTracking(
|
||||
tabKey,
|
||||
@@ -914,6 +962,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -936,7 +987,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
return false;
|
||||
}
|
||||
const { commonInfo, statusInfo, excretaRelatedIndustryList } = response.data;
|
||||
const gridRows = mapListToGridRows(excretaRelatedIndustryList, commonInfo.ctpvNm);
|
||||
const gridRows = mapListToGridRows(excretaRelatedIndustryList, commonInfo.ctpvNm, searchContext);
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
m.set(ExcretaRelatedIndustryInpColumn.tabKey, gridRows);
|
||||
});
|
||||
@@ -945,6 +996,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -970,6 +1024,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
const gridRows = mapListToGridRows(
|
||||
privateSewageTreatRelatedIndustryList,
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
m.set(PrivateSewageTreatRelatedIndustryInpColumn.tabKey, gridRows);
|
||||
@@ -979,6 +1034,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -998,6 +1056,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
setLastSearchedValues(null);
|
||||
@@ -1010,6 +1069,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
setLoadedTabKeys(new Set());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setIsNotApplicableByTab(new Map());
|
||||
setDeleteListByTab(new Map());
|
||||
clearArticleOgnzSnTracking(
|
||||
@@ -1060,7 +1120,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
setIsNotApplicableByTab(new Map());
|
||||
setDeleteListByTab(new Map());
|
||||
clearArticleOgnzSnTracking(
|
||||
@@ -1125,11 +1187,10 @@ const SewageExcretaRelatedInp = () => {
|
||||
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey;
|
||||
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: ctx.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido ?? "",
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -1218,10 +1279,10 @@ const SewageExcretaRelatedInp = () => {
|
||||
}
|
||||
|
||||
commitActiveGrid();
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0);
|
||||
const currentData = getTabGridRows(tabKey);
|
||||
if (NOT_APPLICABLE_TAB_KEYS.has(tabKey)) {
|
||||
if (!noApplicable && currentData.length === 0) {
|
||||
if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -1255,25 +1316,24 @@ const SewageExcretaRelatedInp = () => {
|
||||
|
||||
const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey);
|
||||
const isPublicInstitutionTab = ARTICLE_EDIT_TAB_KEYS.has(tabKey);
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0);
|
||||
const currentData = getTabGridRows(tabKey);
|
||||
const mapRows = rowDataMap.get(tabKey) ?? [];
|
||||
|
||||
const searchContextBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
};
|
||||
|
||||
let submitList:
|
||||
| PublicInstitutionSewerIndustrySaveItem[]
|
||||
| PublicInstitutionExcretaIndustrySaveItem[]
|
||||
| ExcretaRelatedIndustrySaveItem[]
|
||||
| PrivateSewageTreatRelatedIndustrySaveItem[];
|
||||
let deleteList: PublicInstitutionSewerIndustrySaveItem[] = [];
|
||||
let deleteList:
|
||||
| PublicInstitutionSewerIndustrySaveItem[]
|
||||
| PublicInstitutionExcretaIndustrySaveItem[] = [];
|
||||
let sggList: Sgg[] = [];
|
||||
|
||||
if (isDistrictRowTab) {
|
||||
@@ -1305,7 +1365,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
sggList = list;
|
||||
|
||||
const rowsForSubmit = mergeGridRowsWithMapRows(currentData, mapRows, [
|
||||
"ognzSn",
|
||||
PUBLIC_INSTITUTION_OGNZ_SN_FIELD,
|
||||
"admdstCd",
|
||||
"admdstNm",
|
||||
"__isNewArticle",
|
||||
@@ -1327,7 +1387,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
)
|
||||
.map((row) => buildPublicInstitutionIndustrySaveItem(row, list));
|
||||
|
||||
if (!noApplicable && submitList.length === 0) {
|
||||
if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -1341,14 +1401,14 @@ const SewageExcretaRelatedInp = () => {
|
||||
|
||||
const submitRequestBase = {
|
||||
...searchContextBase,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
...(isPublicInstitutionTab ? { noApplicable } : {}),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
|
||||
};
|
||||
@@ -1391,14 +1451,14 @@ const SewageExcretaRelatedInp = () => {
|
||||
tabKey === ExcretaRelatedIndustryInpColumn.tabKey
|
||||
? await statisticsInputSewageExcretaRelatedApi.submitExcretaRelatedIndustry({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as ExcretaRelatedIndustrySaveItem[],
|
||||
})
|
||||
: tabKey === PrivateSewageTreatRelatedIndustryInpColumn.tabKey
|
||||
? await statisticsInputSewageExcretaRelatedApi.submitPrivateSewageTreatRelatedIndustry(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList:
|
||||
submitList as PrivateSewageTreatRelatedIndustrySaveItem[],
|
||||
},
|
||||
@@ -1407,17 +1467,17 @@ const SewageExcretaRelatedInp = () => {
|
||||
? await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionSewerIndustry(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
deleteList,
|
||||
deleteList: deleteList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
},
|
||||
)
|
||||
: await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionExcretaIndustry(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
submitList: submitList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
deleteList,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as PublicInstitutionExcretaIndustrySaveItem[],
|
||||
deleteList: deleteList as PublicInstitutionExcretaIndustrySaveItem[],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1431,15 +1491,16 @@ const SewageExcretaRelatedInp = () => {
|
||||
|
||||
if (finalStatus === "5") {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel("IMR 검증 진행 중...");
|
||||
setGlobalProgressLabel("시계열 검증 진행 중...");
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "",
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1513,18 +1574,14 @@ const SewageExcretaRelatedInp = () => {
|
||||
|
||||
const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey);
|
||||
const isPublicInstitutionTab = ARTICLE_EDIT_TAB_KEYS.has(tabKey);
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0);
|
||||
const currentData = getTabGridRows(tabKey);
|
||||
const mapRows = rowDataMap.get(tabKey) ?? [];
|
||||
|
||||
const baseSaveRequest = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
...(isPublicInstitutionTab && noApplicable
|
||||
? { noApplicable: true as const }
|
||||
: {}),
|
||||
@@ -1534,7 +1591,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const userFields = {
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
|
||||
};
|
||||
|
||||
@@ -1585,7 +1642,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
sggList = list;
|
||||
|
||||
const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [
|
||||
"ognzSn",
|
||||
PUBLIC_INSTITUTION_OGNZ_SN_FIELD,
|
||||
"admdstCd",
|
||||
"admdstNm",
|
||||
"__isNewArticle",
|
||||
@@ -1607,7 +1664,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
)
|
||||
.map((row) => buildPublicInstitutionIndustrySaveItem(row, list));
|
||||
|
||||
if (!noApplicable && saveList.length === 0) {
|
||||
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -1618,16 +1675,16 @@ const SewageExcretaRelatedInp = () => {
|
||||
{
|
||||
...baseSaveRequest,
|
||||
...userFields,
|
||||
saveList,
|
||||
deleteList,
|
||||
saveList: saveList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
deleteList: deleteList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
},
|
||||
)
|
||||
: await statisticsInputSewageExcretaRelatedApi.savePublicInstitutionExcretaIndustry(
|
||||
{
|
||||
...baseSaveRequest,
|
||||
...userFields,
|
||||
saveList,
|
||||
deleteList,
|
||||
saveList: saveList as PublicInstitutionExcretaIndustrySaveItem[],
|
||||
deleteList: deleteList as PublicInstitutionExcretaIndustrySaveItem[],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
@@ -1698,8 +1755,8 @@ const SewageExcretaRelatedInp = () => {
|
||||
newRow.__isNewArticle = 1;
|
||||
if (sggNm) {
|
||||
const { admdstCd, admdstNm } = resolvePublicInstitutionAdmdst(newRow, list);
|
||||
newRow.admdstCd = admdstCd;
|
||||
newRow.admdstNm = admdstNm;
|
||||
newRow.admdstCd = admdstCd ?? "";
|
||||
newRow.admdstNm = admdstNm ?? "";
|
||||
}
|
||||
|
||||
flushSync(() => {
|
||||
@@ -1816,10 +1873,10 @@ const SewageExcretaRelatedInp = () => {
|
||||
for (const tab of SewageExcretaRelatedInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1871,11 +1928,10 @@ const SewageExcretaRelatedInp = () => {
|
||||
[publicInstitutionArticleCellStyleCallback],
|
||||
);
|
||||
|
||||
const searchContextForToolbar = lastSearchedValues ?? searchValues;
|
||||
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
||||
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
||||
const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab);
|
||||
const activeTabToolbar =
|
||||
toolbarSnapshotByTab.get(activeTab) ??
|
||||
computeTabToolbarSnapshot(
|
||||
activeTabStatus,
|
||||
activeTabHasRowData,
|
||||
@@ -1893,10 +1949,13 @@ const SewageExcretaRelatedInp = () => {
|
||||
(activeTabToolbar.showActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab)));
|
||||
const showArticleEditButtons =
|
||||
ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons;
|
||||
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
const showNotApplicableForActiveTab =
|
||||
NOT_APPLICABLE_TAB_KEYS.has(activeTab) &&
|
||||
loadedTabKeys.has(activeTab) &&
|
||||
shouldShowNotApplicable(activeTab, activeTabHasRowData, activeTabStatus);
|
||||
shouldShowNotApplicable(activeTab, activeTabHasRowData, activeTabStatus) &&
|
||||
submitOk;
|
||||
const showSecondaryToolbarCard =
|
||||
hasSearched &&
|
||||
toolbarGroupAllowed &&
|
||||
@@ -1914,16 +1973,14 @@ const SewageExcretaRelatedInp = () => {
|
||||
},
|
||||
[isNotApplicableByTab, setIsNotApplicableForTab],
|
||||
);
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
|
||||
const verificationQueryContext = (() => {
|
||||
const ctx = lastSearchedValues ?? searchValues;
|
||||
const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab;
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -1944,6 +2001,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
@@ -1951,8 +2009,8 @@ const SewageExcretaRelatedInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
@@ -1964,19 +2022,19 @@ const SewageExcretaRelatedInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void lastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showSecondaryToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showNotApplicableForActiveTab && (
|
||||
<NotApplicableCheckButton
|
||||
isCheck={isNotApplicableForActiveTab}
|
||||
@@ -1986,11 +2044,11 @@ const SewageExcretaRelatedInp = () => {
|
||||
{showArticleEditButtons && (
|
||||
<>
|
||||
<AddArticleButton addArticleFunc={() => void addArticleFunc()} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -2018,8 +2076,8 @@ const SewageExcretaRelatedInp = () => {
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(tabStatusInfo, inputPeriodEnterable) ||
|
||||
isAdminInfo;
|
||||
(computeTabEnterable(tabStatusInfo, inputPeriodEnterable) ||
|
||||
isAdminInfo) && inputPeriodEnterable;
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
@@ -2060,6 +2118,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
}
|
||||
getGridRef={activeTab === tabKey ? getGridRef : undefined}
|
||||
cellStyleCallback={resolveGridCellStyleCallback(tabKey)}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
</TabsContent>
|
||||
@@ -2107,7 +2168,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
false,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,19 +1,56 @@
|
||||
import {
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
import { ValueType } from "realgrid";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
import { flushSync } from "react-dom";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { ValueType } from "realgrid";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsInputSewageExcretaSlurgeApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
DigestorOperatingStatusSaveItem,
|
||||
ExcretaSlurgeProcessingStatusSaveItem,
|
||||
SewageExcretaSlurgeRequestParams,
|
||||
SewageExcretaSlurgeVerificationResult,
|
||||
SewageSlurgeProcessingFacStatusSaveItem,
|
||||
SlurgeProcessingFacStatusSaveItem,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface";
|
||||
import {
|
||||
SEWAGE_EXCRETA_DIGESTOR_OPERATING_STATS_CD,
|
||||
SEWAGE_EXCRETA_EXCRETA_SLURGE_PROCESSING_STATS_CD,
|
||||
SEWAGE_EXCRETA_SEWAGE_SLURGE_FAC_STATUS_STATS_CD,
|
||||
SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type { ApiResponse, cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, Sgg, Sido } from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
resolveNoApplicableForRequest,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
import AddArticleButton from "@/components/button/AddArticleButton";
|
||||
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
||||
@@ -24,12 +61,10 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
import type {
|
||||
CellEditedCallback,
|
||||
CellStyleCallback,
|
||||
@@ -41,42 +76,23 @@ import type {
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import {
|
||||
DigestorOperatingStatusInpColumn,
|
||||
DIGESTOR_OPERATING_STATUS_EN_YN_FIELD,
|
||||
DigestorOperatingStatusInpColumn,
|
||||
isDigestorOperatingRowDisabled,
|
||||
} from "@/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn";
|
||||
import { ExcretaSlurgeProcessingStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/ExcretaSlurgeProcessingStatusInpColumn";
|
||||
import { SewageSlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn";
|
||||
import { SlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { statisticsInputSewageExcretaSlurgeApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import {
|
||||
SEWAGE_EXCRETA_DIGESTOR_OPERATING_STATS_CD,
|
||||
SEWAGE_EXCRETA_EXCRETA_SLURGE_PROCESSING_STATS_CD,
|
||||
SEWAGE_EXCRETA_SEWAGE_SLURGE_FAC_STATUS_STATS_CD,
|
||||
SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface";
|
||||
import type {
|
||||
SewageExcretaSlurgeRequestParams,
|
||||
DigestorOperatingStatusSaveItem,
|
||||
ExcretaSlurgeProcessingStatusSaveItem,
|
||||
SewageExcretaSlurgeVerificationResult,
|
||||
SewageSlurgeProcessingFacStatusSaveItem,
|
||||
SlurgeProcessingFacStatusSaveItem,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { ApiResponse, cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, Sgg } from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: SewageExcretaSlurgeVerificationResult;
|
||||
@@ -136,12 +152,12 @@ function verificationCellTooltip({
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR"
|
||||
? "IMR검증 - "
|
||||
? "시계열 검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "HB검증 - ";
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -154,9 +170,8 @@ function buildSewageExcretaSlurgeSearchParam(
|
||||
): SewageExcretaSlurgeRequestParams {
|
||||
return {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
fcltNm: values.fctName?.trim() || undefined,
|
||||
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
||||
};
|
||||
@@ -165,8 +180,9 @@ function buildSewageExcretaSlurgeSearchParam(
|
||||
function mapListToGridRows<T extends { verificationResult?: SewageExcretaSlurgeVerificationResult }>(
|
||||
list: T[] | undefined,
|
||||
ctpvNm: string,
|
||||
values?: SearchFormValues | null,
|
||||
): GridRowWithVerification[] {
|
||||
return (list ?? []).map((item) => {
|
||||
return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
|
||||
const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification;
|
||||
if (item.verificationResult) {
|
||||
row.verificationResult = item.verificationResult;
|
||||
@@ -175,10 +191,10 @@ function mapListToGridRows<T extends { verificationResult?: SewageExcretaSlurgeV
|
||||
});
|
||||
}
|
||||
|
||||
const SewageExcretaSlurgeInpTabList = [
|
||||
SlurgeProcessingFacStatusInpColumn,
|
||||
DigestorOperatingStatusInpColumn,
|
||||
SewageSlurgeProcessingFacStatusInpColumn,
|
||||
const SewageExcretaSlurgeInpTabList = [
|
||||
SlurgeProcessingFacStatusInpColumn,
|
||||
DigestorOperatingStatusInpColumn,
|
||||
SewageSlurgeProcessingFacStatusInpColumn,
|
||||
ExcretaSlurgeProcessingStatusInpColumn,
|
||||
] as const;
|
||||
|
||||
@@ -327,8 +343,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -336,10 +352,22 @@ const computeTabEnterable = (
|
||||
return periodEnterable;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || "")
|
||||
? Boolean(values?.gugun && values.gugun !== "ALL")
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildNoApplicableForRequest(
|
||||
values: SearchFormValues | null,
|
||||
checked: boolean,
|
||||
isSaveListEmpty: boolean,
|
||||
): boolean {
|
||||
return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty);
|
||||
}
|
||||
|
||||
function computeTabToolbarSnapshot(
|
||||
@@ -352,8 +380,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -433,13 +461,6 @@ function mergeGridRowsWithMapRows(
|
||||
});
|
||||
}
|
||||
|
||||
function nullableTrimmedString(
|
||||
value: string | number | null | undefined,
|
||||
): string | null {
|
||||
const s = String(value ?? "").trim();
|
||||
return s === "" ? null : s;
|
||||
}
|
||||
|
||||
function toGridNumber(value: string | number | null | undefined): number {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
@@ -448,7 +469,7 @@ function toGridNumber(value: string | number | null | undefined): number {
|
||||
function resolveSlurgeFacStatusAdmdst(
|
||||
row: Record<string, string | number>,
|
||||
sggOptions: Sgg[],
|
||||
): { admdstCd: string; admdstNm: string } {
|
||||
): { admdstCd: string | null; admdstNm: string | null } {
|
||||
const sggNm = String(row.sggNm ?? "").trim();
|
||||
if (sggNm) {
|
||||
const matched = sggOptions.find((s) => s.gugun === sggNm);
|
||||
@@ -456,12 +477,15 @@ function resolveSlurgeFacStatusAdmdst(
|
||||
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
|
||||
}
|
||||
}
|
||||
const existingCd = String(row.admdstCd ?? "").trim();
|
||||
const existingNm = String(row.admdstNm ?? "").trim();
|
||||
const existingCd = nullableTrimmedString(row.admdstCd);
|
||||
const existingNm = nullableTrimmedString(row.admdstNm);
|
||||
if (existingCd || existingNm) {
|
||||
return { admdstCd: existingCd, admdstNm: existingNm || sggNm };
|
||||
return {
|
||||
admdstCd: existingCd,
|
||||
admdstNm: existingNm ?? nullableTrimmedString(sggNm),
|
||||
};
|
||||
}
|
||||
return { admdstCd: "", admdstNm: sggNm };
|
||||
return { admdstCd: null, admdstNm: nullableTrimmedString(sggNm) };
|
||||
}
|
||||
|
||||
/** 그리드 행 → ST00110001 저장/삭제 API 항목 */
|
||||
@@ -473,46 +497,46 @@ function buildSlurgeProcessingFacStatusSaveItem(
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
swtrmtFcltCd: String(row.swtrmtFcltCd ?? "").trim(),
|
||||
swtrmtFcltNm: String(row.swtrmtFcltNm ?? "").trim(),
|
||||
fcltSn: String(row.fcltSn ?? "").trim(),
|
||||
swtrmtFcltCd: nullableTrimmedString(row.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
itslfQnt: toGridNumber(row.itslfQnt),
|
||||
otsdInflwQnt: toGridNumber(row.otsdInflwQnt),
|
||||
transfTmplntNm: String(row.transfTmplntNm ?? "").trim(),
|
||||
transfTmplntNm: nullableTrimmedString(row.transfTmplntNm),
|
||||
transfPrcsQnt: toGridNumber(row.transfPrcsQnt),
|
||||
srpFncRt: toGridNumber(row.srpFncRt),
|
||||
itslfTs1RclmQnt: toGridNumber(row.itslfTs1RclmQnt),
|
||||
itslfTs1IncnrQnt: toGridNumber(row.itslfTs1IncnrQnt),
|
||||
itslfTs1AltdQnt: toGridNumber(row.itslfTs1AltdQnt),
|
||||
itslfTs1DryQnt: toGridNumber(row.itslfTs1DryQnt),
|
||||
itslfTs1FuelQnt: toGridNumber(row.itslfTs1FuelQnt),
|
||||
itslfTs2CmpstgQnt: toGridNumber(row.itslfTs2CmpstgQnt),
|
||||
cmpsPrcsFcltBzentyNm: String(row.cmpsPrcsFcltBzentyNm ?? "").trim(),
|
||||
sludgeFncRt: toGridNumber(row.sludgeFncRt),
|
||||
itslfFrlvlRclmQnt: toGridNumber(row.itslfFrlvlRclmQnt),
|
||||
itslfFrlvlIncnrQnt: toGridNumber(row.itslfFrlvlIncnrQnt),
|
||||
itslfFrlvlSldQnt: toGridNumber(row.itslfFrlvlSldQnt),
|
||||
itslfFrlvlDryQnt: toGridNumber(row.itslfFrlvlDryQnt),
|
||||
itslfFrlvlCrbnQnt: toGridNumber(row.itslfFrlvlCrbnQnt),
|
||||
itslfFrlvlCmpstgQnt: toGridNumber(row.itslfFrlvlCmpstgQnt),
|
||||
cmpsPrcsFcltBzentyNm: nullableTrimmedString(row.cmpsPrcsFcltBzentyNm),
|
||||
incnrPrcsLfvlm: toGridNumber(row.incnrPrcsLfvlm),
|
||||
incnrPrcsPrdctRmtrlQnt: toGridNumber(row.incnrPrcsPrdctRmtrlQnt),
|
||||
incnrPrcsFmlyrQnt: toGridNumber(row.incnrPrcsFmlyrQnt),
|
||||
incnrPrcsFcltNm: String(row.incnrPrcsFcltNm ?? "").trim(),
|
||||
stbznPrcsLfvlm: toGridNumber(row.stbznPrcsLfvlm),
|
||||
stbznPrcsFmlyrQnt: toGridNumber(row.stbznPrcsFmlyrQnt),
|
||||
stbznPrcsFcltNm: String(row.stbznPrcsFcltNm ?? "").trim(),
|
||||
incnrPrcsCwthQnt: toGridNumber(row.incnrPrcsCwthQnt),
|
||||
incnrPrcsFcltNm: nullableTrimmedString(row.incnrPrcsFcltNm),
|
||||
sldPrcsLfvlm: toGridNumber(row.sldPrcsLfvlm),
|
||||
sldPrcsCwthQnt: toGridNumber(row.sldPrcsCwthQnt),
|
||||
sldPrcsFcltNm: nullableTrimmedString(row.sldPrcsFcltNm),
|
||||
dryPrcsLfvlm: toGridNumber(row.dryPrcsLfvlm),
|
||||
dryPrcsIncnrQnt: toGridNumber(row.dryPrcsIncnrQnt),
|
||||
dryPrcsFuelQnt: toGridNumber(row.dryPrcsFuelQnt),
|
||||
dryPrcsPrdctRmtrlQnt: toGridNumber(row.dryPrcsPrdctRmtrlQnt),
|
||||
dryPrcsFmlyrQnt: toGridNumber(row.dryPrcsFmlyrQnt),
|
||||
dryPrcsCwthQnt: toGridNumber(row.dryPrcsCwthQnt),
|
||||
dryPrcsCmpstgQnt: toGridNumber(row.dryPrcsCmpstgQnt),
|
||||
dryPrcsFcltNm: String(row.dryPrcsFcltNm ?? "").trim(),
|
||||
fuelPrcsIncnrQnt: toGridNumber(row.fuelPrcsIncnrQnt),
|
||||
fuelPrcsFuelQnt: toGridNumber(row.fuelPrcsFuelQnt),
|
||||
fuelPrcsFcltNm: String(row.fuelPrcsFcltNm ?? "").trim(),
|
||||
dryPrcsFcltNm: nullableTrimmedString(row.dryPrcsFcltNm),
|
||||
crbnPrcsIncnrQnt: toGridNumber(row.crbnPrcsIncnrQnt),
|
||||
crbnPrcsFuelQnt: toGridNumber(row.crbnPrcsFuelQnt),
|
||||
crbnPrcsFcltNm: nullableTrimmedString(row.crbnPrcsFcltNm),
|
||||
otsdCnsgnRclmQnt: toGridNumber(row.otsdCnsgnRclmQnt),
|
||||
otsdCnsgnIncnrQnt: toGridNumber(row.otsdCnsgnIncnrQnt),
|
||||
otsdCnsgnFuelPwplQnt: toGridNumber(row.otsdCnsgnFuelPwplQnt),
|
||||
otsdCnsgnPwplQnt: toGridNumber(row.otsdCnsgnPwplQnt),
|
||||
otsdCnsgnPrdctRmtrlQnt: toGridNumber(row.otsdCnsgnPrdctRmtrlQnt),
|
||||
otsdCnsgnFmlyrQnt: toGridNumber(row.otsdCnsgnFmlyrQnt),
|
||||
otsdCnsgnCwthQnt: toGridNumber(row.otsdCnsgnCwthQnt),
|
||||
otsdCnsgnCmpstgQnt: toGridNumber(row.otsdCnsgnCmpstgQnt),
|
||||
otsdCnsgnFcltNm: String(row.otsdCnsgnFcltNm ?? "").trim(),
|
||||
cparTs1Whol: toGridNumber(row.cparTs1Whol),
|
||||
otsdCnsgnFcltNm: nullableTrimmedString(row.otsdCnsgnFcltNm),
|
||||
cparFrlvlWholQnt: toGridNumber(row.cparFrlvlWholQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -526,26 +550,26 @@ function buildDigestorOperatingStatusSaveItem(
|
||||
const base = {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
swtrmtFcltCd: String(row.swtrmtFcltCd ?? "").trim(),
|
||||
swtrmtFcltNm: String(row.swtrmtFcltNm ?? "").trim(),
|
||||
fcltSn: String(row.fcltSn ?? "").trim(),
|
||||
digstnEnYn: String(row.digstnEnYn ?? "").trim(),
|
||||
swtrmtFcltCd: nullableTrimmedString(row.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
gasPrcsEnYn: nullableTrimmedString(row.gasPrcsEnYn),
|
||||
};
|
||||
|
||||
if (isDigestorOperatingRowDisabled(row)) {
|
||||
return {
|
||||
...base,
|
||||
digstnEnYn: "N",
|
||||
digstnCpct: 0,
|
||||
gasPrcsEnYn: "N",
|
||||
gasPrcsCpct: 0,
|
||||
actlDrvnCpct: 0,
|
||||
digstnGasInputQnt: 0,
|
||||
digstnGasInputSldDnst: 0,
|
||||
digstnTnkOprtngDayCnt: 0,
|
||||
prcsGasInputQnt: 0,
|
||||
prcsGasInputSldDnst: 0,
|
||||
prcsRepoOprtngDayCnt: 0,
|
||||
ocrnGasQnt: 0,
|
||||
digstnGasUtztnNtslQnt: 0,
|
||||
digstnGasUtztnEtgtQnt: 0,
|
||||
digstnGasUtztnItslfQnt: 0,
|
||||
digstnGasUtztnEtcQnt: 0,
|
||||
prcsGasUtztnNtslQnt: 0,
|
||||
prcsGasUtztnEtgtQnt: 0,
|
||||
prcsGasUtztnItslfQnt: 0,
|
||||
prcsGasUtztnEtcQnt: 0,
|
||||
surplsGasQnt: 0,
|
||||
rmrk: null,
|
||||
};
|
||||
@@ -553,16 +577,16 @@ function buildDigestorOperatingStatusSaveItem(
|
||||
|
||||
return {
|
||||
...base,
|
||||
digstnCpct: toGridNumber(row.digstnCpct),
|
||||
gasPrcsCpct: toGridNumber(row.gasPrcsCpct),
|
||||
actlDrvnCpct: toGridNumber(row.actlDrvnCpct),
|
||||
digstnGasInputQnt: toGridNumber(row.digstnGasInputQnt),
|
||||
digstnGasInputSldDnst: toGridNumber(row.digstnGasInputSldDnst),
|
||||
digstnTnkOprtngDayCnt: toGridNumber(row.digstnTnkOprtngDayCnt),
|
||||
prcsGasInputQnt: toGridNumber(row.prcsGasInputQnt),
|
||||
prcsGasInputSldDnst: toGridNumber(row.prcsGasInputSldDnst),
|
||||
prcsRepoOprtngDayCnt: toGridNumber(row.prcsRepoOprtngDayCnt),
|
||||
ocrnGasQnt: toGridNumber(row.ocrnGasQnt),
|
||||
digstnGasUtztnNtslQnt: toGridNumber(row.digstnGasUtztnNtslQnt),
|
||||
digstnGasUtztnEtgtQnt: toGridNumber(row.digstnGasUtztnEtgtQnt),
|
||||
digstnGasUtztnItslfQnt: toGridNumber(row.digstnGasUtztnItslfQnt),
|
||||
digstnGasUtztnEtcQnt: toGridNumber(row.digstnGasUtztnEtcQnt),
|
||||
prcsGasUtztnNtslQnt: toGridNumber(row.prcsGasUtztnNtslQnt),
|
||||
prcsGasUtztnEtgtQnt: toGridNumber(row.prcsGasUtztnEtgtQnt),
|
||||
prcsGasUtztnItslfQnt: toGridNumber(row.prcsGasUtztnItslfQnt),
|
||||
prcsGasUtztnEtcQnt: toGridNumber(row.prcsGasUtztnEtcQnt),
|
||||
surplsGasQnt: toGridNumber(row.surplsGasQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
@@ -577,13 +601,13 @@ function buildSewageSlurgeProcessingFacStatusSaveItem(
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
fcltSn: String(row.fcltSn ?? "").trim(),
|
||||
fcltNm: String(row.fcltNm ?? "").trim(),
|
||||
prcsMthExpln: String(row.prcsMthExpln ?? "").trim(),
|
||||
fcltCpct: toGridNumber(row.fcltCpct),
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
fcltNm: nullableTrimmedString(row.fcltNm),
|
||||
prcsMthExpln: nullableTrimmedString(row.prcsMthExpln),
|
||||
sludgeFcltCpct: toGridNumber(row.sludgeFcltCpct),
|
||||
anlPrcsQnt: toGridNumber(row.anlPrcsQnt),
|
||||
dayAvgOprtngHr: toGridNumber(row.dayAvgOprtngHr),
|
||||
anlOprtngDayCnt: toGridNumber(row.anlOprtngDayCnt),
|
||||
dyrgOprtngHr: toGridNumber(row.dyrgOprtngHr),
|
||||
fcltAnlOprtngDayCnt: toGridNumber(row.fcltAnlOprtngDayCnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -597,17 +621,17 @@ function buildExcretaSlurgeProcessingStatusSaveItem(
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
fcltSn: String(row.fcltSn ?? "").trim(),
|
||||
fcltNm: String(row.fcltNm ?? "").trim(),
|
||||
fcltPstnCn: String(row.fcltPstnCn ?? "").trim(),
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
fcltNm: nullableTrimmedString(row.fcltNm),
|
||||
fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
|
||||
rcycQnt: toGridNumber(row.rcycQnt),
|
||||
olandLfvlm: toGridNumber(row.olandLfvlm),
|
||||
incnrQnt: toGridNumber(row.incnrQnt),
|
||||
oceanInputQnt: toGridNumber(row.oceanInputQnt),
|
||||
fuelQnt: toGridNumber(row.fuelQnt),
|
||||
etcQnt: toGridNumber(row.etcQnt),
|
||||
nondspsQnt: toGridNumber(row.nondspsQnt),
|
||||
swtrmtFcltNm: String(row.swtrmtFcltNm ?? "").trim(),
|
||||
unprcsQnt: toGridNumber(row.unprcsQnt),
|
||||
swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -630,15 +654,17 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
// 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리
|
||||
const SewageExcretaSlurgeInp = () => {
|
||||
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
const { confirm } = useConfirm();
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
@@ -667,9 +693,15 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
||||
const [loadedTabKeys, setLoadedTabKeys] = useState<Set<string>>(() => new Set());
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
@@ -700,36 +732,29 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const preYearArticleFcltSnRef = useRef<ArticleFcltSnSet>(new Set());
|
||||
activeTabRef.current = activeTab;
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_SLURGE) return;
|
||||
const tabKey = activeTab;
|
||||
if (!tabKey) return;
|
||||
if (Object.prototype.hasOwnProperty.call(columnGuidesByTab, tabKey)) return;
|
||||
const statsCd = tabKey;
|
||||
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
SewageExcretaSlurgeInpTabList.map(async (tab) => {
|
||||
const tabKey = tab.tabKey;
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(tabKey);
|
||||
if (res.status !== "success" || !Array.isArray(res.data)) {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [tabKey, next] as const;
|
||||
} catch {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
const next = await fetchColumnGuidesRecord(statsCd);
|
||||
if (cancelled) return;
|
||||
setColumnGuidesByTab(Object.fromEntries(entries));
|
||||
setColumnGuidesByTab((prev) =>
|
||||
Object.prototype.hasOwnProperty.call(prev, tabKey)
|
||||
? prev
|
||||
: { ...prev, [tabKey]: next },
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [activeTabId, activeTab, columnGuidesByTab]);
|
||||
|
||||
const shouldShowNotApplicable = (
|
||||
hasRows: boolean,
|
||||
@@ -789,6 +814,13 @@ const SewageExcretaSlurgeInp = () => {
|
||||
);
|
||||
return next;
|
||||
});
|
||||
if (searchContext) {
|
||||
setSearchContextByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, searchContext);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getTabGridRows = (tabKey: string): Record<string, string | number>[] =>
|
||||
@@ -922,7 +954,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
return false;
|
||||
}
|
||||
const { commonInfo, statusInfo, slurgeProcessingFacStatusList } = response.data;
|
||||
const gridRows = mapListToGridRows(slurgeProcessingFacStatusList, commonInfo.ctpvNm);
|
||||
const gridRows = mapListToGridRows(slurgeProcessingFacStatusList, commonInfo.ctpvNm, searchContext);
|
||||
syncSlurgeFacStatusArticleFcltSnTracking(gridRows, fromPreYear);
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
m.set(SlurgeProcessingFacStatusInpColumn.tabKey, gridRows);
|
||||
@@ -932,6 +964,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -954,7 +989,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
return false;
|
||||
}
|
||||
const { commonInfo, statusInfo, digestorOperatingStatusList } = response.data;
|
||||
const gridRows = mapListToGridRows(digestorOperatingStatusList, commonInfo.ctpvNm);
|
||||
const gridRows = mapListToGridRows(digestorOperatingStatusList, commonInfo.ctpvNm, searchContext);
|
||||
if (!fromPreYear) {
|
||||
publicSewageFacilitiesAvailableRef.current = gridRows.length > 0;
|
||||
if (gridRows.length === 0 && overrides?.alertIfDigestorEmpty) {
|
||||
@@ -969,6 +1004,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -995,6 +1033,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const gridRows = mapListToGridRows(
|
||||
sewageSlurgeProcessingFacStatusList,
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
m.set(SewageSlurgeProcessingFacStatusInpColumn.tabKey, gridRows);
|
||||
@@ -1004,6 +1043,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -1029,6 +1071,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const gridRows = mapListToGridRows(
|
||||
excretaSlurgeProcessingStatusList,
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
if (!fromPreYear) {
|
||||
excretaTreatmentFacilitiesAvailableRef.current = gridRows.length > 0;
|
||||
@@ -1044,6 +1087,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -1065,7 +1111,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
setLastSearchedValues(null);
|
||||
@@ -1123,7 +1171,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
loadedTabKeysRef.current.clear();
|
||||
setLoadedTabKeys(new Set());
|
||||
setDeleteListByTab(new Map());
|
||||
@@ -1232,9 +1282,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
}
|
||||
|
||||
commitActiveGrid();
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0);
|
||||
const currentData = getTabGridRows(tabKey);
|
||||
if (!noApplicable && currentData.length === 0) {
|
||||
if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -1262,7 +1312,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
|
||||
commitActiveGrid();
|
||||
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0);
|
||||
const currentData = getTabGridRows(tabKey);
|
||||
const mapRows = rowDataMap.get(tabKey) ?? [];
|
||||
|
||||
@@ -1320,6 +1370,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
|
||||
if (
|
||||
!noApplicable &&
|
||||
buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues) &&
|
||||
((isSlurgeFacTab && slurgeSubmitList.length === 0) ||
|
||||
(isDigestorTab && digestorSubmitList.length === 0) ||
|
||||
(isSewageSlurgeFacTab && sewageSlurgeFacSubmitList.length === 0) ||
|
||||
@@ -1341,12 +1392,8 @@ const SewageExcretaSlurgeInp = () => {
|
||||
: SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD);
|
||||
|
||||
const verificationQuery = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
statsCd: statsCdForSubmit,
|
||||
};
|
||||
@@ -1375,20 +1422,16 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const submitRequestBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
userId: "",
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userUuid: "",
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
|
||||
noApplicable,
|
||||
};
|
||||
@@ -1400,21 +1443,21 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const response = isSlurgeFacTab
|
||||
? await statisticsInputSewageExcretaSlurgeApi.submitSlurgeProcessingFacStatus({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: slurgeSubmitList,
|
||||
deleteList: slurgeDeleteList,
|
||||
})
|
||||
: isDigestorTab
|
||||
? await statisticsInputSewageExcretaSlurgeApi.submitDigestorOperatingStatus({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: digestorSubmitList,
|
||||
})
|
||||
: isSewageSlurgeFacTab
|
||||
? await statisticsInputSewageExcretaSlurgeApi.submitSewageSlurgeProcessingFacStatus(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: sewageSlurgeFacSubmitList,
|
||||
deleteList: sewageSlurgeFacDeleteList,
|
||||
},
|
||||
@@ -1422,7 +1465,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
: await statisticsInputSewageExcretaSlurgeApi.submitExcretaSlurgeProcessingStatus(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: excretaSlurgeSubmitList,
|
||||
},
|
||||
);
|
||||
@@ -1437,15 +1480,16 @@ const SewageExcretaSlurgeInp = () => {
|
||||
|
||||
if (finalStatus === "5") {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel("IMR 검증 진행 중...");
|
||||
setGlobalProgressLabel("시계열 검증 진행 중...");
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "",
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForSubmit,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1517,14 +1561,10 @@ const SewageExcretaSlurgeInp = () => {
|
||||
}
|
||||
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -1570,7 +1610,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
|
||||
commitActiveGrid();
|
||||
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0);
|
||||
const currentData = getTabGridRows(tabKey);
|
||||
const mapRows = rowDataMap.get(tabKey) ?? [];
|
||||
|
||||
@@ -1588,11 +1628,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const baseSaveRequest = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
...(noApplicable ? { noApplicable: true } : {}),
|
||||
};
|
||||
|
||||
@@ -1600,7 +1636,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const userFields = {
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
|
||||
};
|
||||
|
||||
@@ -1623,7 +1659,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
.filter((row) => isPersistedArticleRowForTab(tabKey, row))
|
||||
.map((row) => buildSlurgeProcessingFacStatusSaveItem(row, list));
|
||||
|
||||
if (!noApplicable && saveList.length === 0) {
|
||||
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -1650,7 +1686,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
buildDigestorOperatingStatusSaveItem(row, list),
|
||||
);
|
||||
|
||||
if (!noApplicable && saveList.length === 0) {
|
||||
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -1676,7 +1712,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
.filter((row) => isPersistedArticleRowForTab(tabKey, row))
|
||||
.map((row) => buildSewageSlurgeProcessingFacStatusSaveItem(row, list));
|
||||
|
||||
if (!noApplicable && saveList.length === 0) {
|
||||
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -1703,7 +1739,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
buildExcretaSlurgeProcessingStatusSaveItem(row, list),
|
||||
);
|
||||
|
||||
if (!noApplicable && saveList.length === 0) {
|
||||
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -1881,10 +1917,10 @@ const SewageExcretaSlurgeInp = () => {
|
||||
for (const tab of SewageExcretaSlurgeInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1898,7 +1934,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
col.fieldName === "sggNm"
|
||||
) {
|
||||
next = buildSggNmColumn(col, canEdit, sggOptions);
|
||||
} else if (col.fieldName === "sggNm" || col.fieldName === "swtrmtFcltNm") {
|
||||
} else if (col.fieldName === "sggNm") {
|
||||
next = { ...col, editable: false as const, styleName: "rg-cell-readonly" };
|
||||
}
|
||||
const guide = columnGuidesByTab[tabKey]?.[col.fieldName]?.trim();
|
||||
@@ -1962,7 +1998,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
[digestorOperatingCellStyleCallback],
|
||||
);
|
||||
|
||||
const searchContextForToolbar = lastSearchedValues ?? searchValues;
|
||||
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
||||
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
||||
const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab);
|
||||
const activeTabToolbar = computeTabToolbarSnapshot(
|
||||
@@ -1983,6 +2019,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const showSaveInToolbar = baseShowSaveInToolbar && toolbarGroupAllowed;
|
||||
const showEnterableToolbarButtons =
|
||||
baseShowEnterableToolbarButtons && toolbarGroupAllowed;
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
const showActionToolbarCard =
|
||||
toolbarGroupAllowed &&
|
||||
(baseShowActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab)));
|
||||
@@ -1990,7 +2027,8 @@ const SewageExcretaSlurgeInp = () => {
|
||||
ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons;
|
||||
const showNotApplicableForActiveTab =
|
||||
loadedTabKeys.has(activeTab) &&
|
||||
shouldShowNotApplicable(activeTabHasRowData, activeTabStatus);
|
||||
shouldShowNotApplicable(activeTabHasRowData, activeTabStatus) &&
|
||||
submitOk;
|
||||
const showSecondaryToolbarCard =
|
||||
hasSearched &&
|
||||
toolbarGroupAllowed &&
|
||||
@@ -2024,9 +2062,8 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const statsCd = tabStatsCdMap.get(activeTab);
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -2047,6 +2084,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName
|
||||
@@ -2055,8 +2093,8 @@ const SewageExcretaSlurgeInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
@@ -2068,19 +2106,19 @@ const SewageExcretaSlurgeInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void lastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showSecondaryToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showNotApplicableForActiveTab && (
|
||||
<NotApplicableCheckButton
|
||||
isCheck={isNotApplicableForActiveTab}
|
||||
@@ -2089,12 +2127,12 @@ const SewageExcretaSlurgeInp = () => {
|
||||
)}
|
||||
{showArticleEditButtons && (
|
||||
<>
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -2159,6 +2197,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
activeTab === tabKey ? getGridRef : undefined
|
||||
}
|
||||
cellStyleCallback={resolveGridCellStyleCallback(tabKey)}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
onCellEdited={
|
||||
tabKey === DigestorOperatingStatusInpColumn.tabKey
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,12 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import {
|
||||
STATISTICS_INPUT_URL_PATH,
|
||||
verificationSearchToFormValues,
|
||||
} from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { sewagePipeMaintainApiService } from "@/entities/statistics/api/input/statistics-input-sewage-pipe-maintain-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -27,7 +27,15 @@ import type {
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sido,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
|
||||
import {
|
||||
LoadingWithAnimation,
|
||||
Tabs,
|
||||
@@ -46,18 +54,23 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { PipeDredgingStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/PipeDredgingStatusInpColumn";
|
||||
import { RepairStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/RepairStatusInpColumn";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -68,9 +81,8 @@ function buildSewagePipeMaintainSearchParam(
|
||||
): SewagePipeMaintainRequestParams {
|
||||
return {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
||||
};
|
||||
}
|
||||
@@ -82,12 +94,6 @@ const SewagePipeMaintainInpTabList = [
|
||||
|
||||
const firstTabKey = RepairStatusInpColumn.tabKey;
|
||||
|
||||
const SEWAGE_PIPE_MAINTAIN_COLUMN_GUIDE_TAB_KEYS = Array.from(
|
||||
new Set(
|
||||
SewagePipeMaintainInpTabList.map((tab) => tab.tabKey).filter(Boolean),
|
||||
),
|
||||
);
|
||||
|
||||
const toGridColumnInfo = (tab: (typeof SewagePipeMaintainInpTabList)[number]): ColumnInfo => ({
|
||||
columns: tab.columns ?? [],
|
||||
columnLayout: tab.columnLayout ?? [],
|
||||
@@ -107,8 +113,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -116,10 +122,14 @@ const computeTabEnterable = (
|
||||
return periodEnterable;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || "")
|
||||
? Boolean(values?.gugun && values.gugun !== "ALL")
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function computeTabToolbarSnapshot(
|
||||
@@ -132,8 +142,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -154,17 +164,6 @@ function tabRowDataExists(
|
||||
return Array.isArray(rows) && rows.length > 0;
|
||||
}
|
||||
|
||||
function normalizeGridString(value: unknown): string {
|
||||
if (value === null || value === undefined) return "";
|
||||
return typeof value === "string" ? value : String(value);
|
||||
}
|
||||
|
||||
function normalizeGridRmrk(value: unknown): string | null {
|
||||
if (value === null || value === undefined) return null;
|
||||
const s = typeof value === "string" ? value : String(value);
|
||||
return s.trim() === "" ? null : s;
|
||||
}
|
||||
|
||||
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
async function waitForImrJobCompleted(
|
||||
@@ -183,11 +182,11 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
function buildColumnFormatNumber(
|
||||
@@ -218,25 +217,26 @@ function buildRepairRowList(
|
||||
const formatNumber = buildColumnFormatNumber(RepairStatusInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
sggNm: normalizeGridString(d.sggNm),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
mendngWhol: formatNumber(d.mendngWhol, "mendngWhol"),
|
||||
cbsysMendngPrlg: formatNumber(d.cbsysMendngPrlg, "cbsysMendngPrlg"),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
dfrpWholPrlg: formatNumber(d.dfrpWholPrlg, "dfrpWholPrlg"),
|
||||
cbsysDfrpPrlg: formatNumber(d.cbsysDfrpPrlg, "cbsysDfrpPrlg"),
|
||||
sprsysExlnPrlg: formatNumber(d.sprsysExlnPrlg, "sprsysExlnPrlg"),
|
||||
sprsysDrtwPrlg: formatNumber(d.sprsysDrtwPrlg, "sprsysDrtwPrlg"),
|
||||
cbsysPartMendngPrlg: formatNumber(d.cbsysPartMendngPrlg, "cbsysPartMendngPrlg"),
|
||||
sprsysExlnMendngPrlg: formatNumber(d.sprsysExlnMendngPrlg, "sprsysExlnMendngPrlg"),
|
||||
sprsysDrtwMendngPrlg: formatNumber(d.sprsysDrtwMendngPrlg, "sprsysDrtwMendngPrlg"),
|
||||
partDfrpWhol: formatNumber(d.partDfrpWhol, "partDfrpWhol"),
|
||||
cbsysPartDfrpPrlg: formatNumber(d.cbsysPartDfrpPrlg, "cbsysPartDfrpPrlg"),
|
||||
sprsysExlnDfrpPrlg: formatNumber(d.sprsysExlnDfrpPrlg, "sprsysExlnDfrpPrlg"),
|
||||
sprsysDrtwDfrpPrlg: formatNumber(d.sprsysDrtwDfrpPrlg, "sprsysDrtwDfrpPrlg"),
|
||||
cbsysMnhlCnt: formatNumber(d.cbsysMnhlCnt, "cbsysMnhlCnt"),
|
||||
sprsysDrtwMnhlCnt: formatNumber(d.sprsysDrtwMnhlCnt, "sprsysDrtwMnhlCnt"),
|
||||
sprsysExlnMnhlCnt: formatNumber(d.sprsysExlnMnhlCnt, "sprsysExlnMnhlCnt"),
|
||||
drtwRcvprpCnt: formatNumber(d.drtwRcvprpCnt, "drtwRcvprpCnt"),
|
||||
exlnRcvprpCnt: formatNumber(d.exlnRcvprpCnt, "exlnRcvprpCnt"),
|
||||
drtwDrgCnt: formatNumber(d.drtwDrgCnt, "drtwDrgCnt"),
|
||||
exlnDrgCnt: formatNumber(d.exlnDrgCnt, "exlnDrgCnt"),
|
||||
stofcbrCnt: formatNumber(d.stofcbrCnt, "stofcbrCnt"),
|
||||
otflCnt: formatNumber(d.otflCnt, "otflCnt"),
|
||||
cctvExmnPrlg: formatNumber(d.cctvExmnPrlg, "cctvExmnPrlg"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -246,34 +246,36 @@ function buildDredgingRowList(
|
||||
const formatNumber = buildColumnFormatNumber(PipeDredgingStatusInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
sggNm: normalizeGridString(d.sggNm),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"),
|
||||
wholSwagePpl: formatNumber(d.wholSwagePpl, "wholSwagePpl"),
|
||||
cbsysPplPrlg: formatNumber(d.cbsysPplPrlg, "cbsysPplPrlg"),
|
||||
sprsysExlnPrlg: formatNumber(d.sprsysExlnPrlg, "sprsysExlnPrlg"),
|
||||
sprsysDrtwPrlg: formatNumber(d.sprsysDrtwPrlg, "sprsysDrtwPrlg"),
|
||||
mnhlCnt: formatNumber(d.mnhlCnt, "mnhlCnt"),
|
||||
exlnDrtwRcvprpCnt: formatNumber(d.exlnDrtwRcvprpCnt, "exlnDrtwRcvprpCnt"),
|
||||
exlnDrtwDrgCnt: formatNumber(d.exlnDrtwDrgCnt, "exlnDrtwDrgCnt"),
|
||||
stofcbrOtflCnt: formatNumber(d.stofcbrOtflCnt, "stofcbrOtflCnt"),
|
||||
drdgnPlanPrlg: formatNumber(d.drdgnPlanPrlg, "drdgnPlanPrlg"),
|
||||
drdgnTrgtCtrsDrdgnRt: formatNumber(d.drdgnTrgtCtrsDrdgnRt, "drdgnTrgtCtrsDrdgnRt"),
|
||||
wholPrlgCtrsDrdgnRt: formatNumber(d.wholPrlgCtrsDrdgnRt, "wholPrlgCtrsDrdgnRt"),
|
||||
drdgnAmt: formatNumber(d.drdgnAmt, "drdgnAmt"),
|
||||
drdgnQnt: formatNumber(d.drdgnQnt, "drdgnQnt"),
|
||||
drdgnUntprcAmt: formatNumber(d.drdgnUntprcAmt, "drdgnUntprcAmt"),
|
||||
pplDrdqty: formatNumber(d.pplDrdqty, "pplDrdqty"),
|
||||
drdgnSoilPrcsAmt: formatNumber(d.drdgnSoilPrcsAmt, "drdgnSoilPrcsAmt"),
|
||||
wholDrdgnPrcsQnt: formatNumber(d.wholDrdgnPrcsQnt, "wholDrdgnPrcsQnt"),
|
||||
rclmQnt: formatNumber(d.rclmQnt, "rclmQnt"),
|
||||
oceanEmsnQnt: formatNumber(d.oceanEmsnQnt, "oceanEmsnQnt"),
|
||||
rcycQnt: formatNumber(d.rcycQnt, "rcycQnt"),
|
||||
etcQnt: formatNumber(d.etcQnt, "etcQnt"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
|
||||
// 하수도 통계 관리 > 03. 하수관로 유지관리
|
||||
const SewagePipeMaintainInp = () => {
|
||||
const { sidoList, isReady } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
const toolbarGroupAllowed = useStatisticsInputToolbarGroupGate();
|
||||
@@ -296,9 +298,15 @@ const SewagePipeMaintainInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
|
||||
const [showSubmitDialog, setShowSubmitDialog] = useState(false);
|
||||
@@ -328,6 +336,7 @@ const SewagePipeMaintainInp = () => {
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN) return;
|
||||
if (!isReady) return;
|
||||
const pending = useStatisticsInputFocusStore
|
||||
.getState()
|
||||
.takePendingForUrl(STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN);
|
||||
@@ -335,43 +344,34 @@ const SewagePipeMaintainInp = () => {
|
||||
const { tabKey, search } = pending;
|
||||
setActiveTab(tabKey);
|
||||
if (search) {
|
||||
const values = verificationSearchToFormValues(search);
|
||||
const values = verificationSearchToFormValues(search, sidoList);
|
||||
setVerificationSearchDefaults(values);
|
||||
postVerificationSearchRef.current = { values, tabKey };
|
||||
setSearchFormRemountKey((k) => k + 1);
|
||||
}
|
||||
}, [activeTabId, inputFocusNonce]);
|
||||
}, [activeTabId, inputFocusNonce, isReady, sidoList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN) return;
|
||||
const tabKey = activeTab;
|
||||
if (!tabKey) return;
|
||||
if (Object.prototype.hasOwnProperty.call(columnGuidesByTab, tabKey)) return;
|
||||
const statsCd = tabKey;
|
||||
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
SEWAGE_PIPE_MAINTAIN_COLUMN_GUIDE_TAB_KEYS.map(async (tabKey) => {
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(tabKey);
|
||||
if (res.status !== "success" || !Array.isArray(res.data)) {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [tabKey, next] as const;
|
||||
} catch {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
const next = await fetchColumnGuidesRecord(statsCd);
|
||||
if (cancelled) return;
|
||||
setColumnGuidesByTab(Object.fromEntries(entries));
|
||||
setColumnGuidesByTab((prev) =>
|
||||
Object.prototype.hasOwnProperty.call(prev, tabKey)
|
||||
? prev
|
||||
: { ...prev, [tabKey]: next },
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [activeTabId]);
|
||||
}, [activeTabId, activeTab, columnGuidesByTab]);
|
||||
|
||||
const commitToolbarSnapshotForLoadedTab = (
|
||||
tabKey: string,
|
||||
@@ -394,6 +394,13 @@ const SewagePipeMaintainInp = () => {
|
||||
);
|
||||
return next;
|
||||
});
|
||||
if (searchContext) {
|
||||
setSearchContextByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, searchContext);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const applyRowMap = (
|
||||
@@ -435,7 +442,11 @@ const SewagePipeMaintainInp = () => {
|
||||
return false;
|
||||
}
|
||||
const { commonInfo, statusInfo, repairStatusList } = response.data;
|
||||
const convertedData = (repairStatusList ?? []).map((d) => ({
|
||||
const convertedData = filterBySelectedAdmdstCd(
|
||||
repairStatusList ?? [],
|
||||
sidoListRef.current,
|
||||
searchContext,
|
||||
).map((d) => ({
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
...d,
|
||||
}));
|
||||
@@ -449,6 +460,9 @@ const SewagePipeMaintainInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(searchContext);
|
||||
}
|
||||
@@ -471,7 +485,11 @@ const SewagePipeMaintainInp = () => {
|
||||
return false;
|
||||
}
|
||||
const { commonInfo, statusInfo, pipeDredgingStatusList } = response.data;
|
||||
const convertedData = (pipeDredgingStatusList ?? []).map((d) => ({
|
||||
const convertedData = filterBySelectedAdmdstCd(
|
||||
pipeDredgingStatusList ?? [],
|
||||
sidoListRef.current,
|
||||
searchContext,
|
||||
).map((d) => ({
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
...d,
|
||||
}));
|
||||
@@ -485,6 +503,9 @@ const SewagePipeMaintainInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(searchContext);
|
||||
}
|
||||
@@ -592,28 +613,20 @@ const SewagePipeMaintainInp = () => {
|
||||
const submitInfoBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
};
|
||||
|
||||
const verificationQuery = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
@@ -636,12 +649,12 @@ const SewagePipeMaintainInp = () => {
|
||||
const response = isRepairTab
|
||||
? await sewagePipeMaintainApiService.submitSewagePipeMaintainRepair({
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildRepairRowList(currentData),
|
||||
} satisfies SewagePipeMaintainRepairSubmitRequest)
|
||||
: await sewagePipeMaintainApiService.submitSewagePipeMaintainDredging({
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildDredgingRowList(currentData),
|
||||
} satisfies SewagePipeMaintainDredgingSubmitRequest);
|
||||
|
||||
@@ -655,14 +668,15 @@ const SewagePipeMaintainInp = () => {
|
||||
|
||||
if (finalStatus === "5") {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel("IMR 검증 진행 중...");
|
||||
setGlobalProgressLabel("시계열 검증 진행 중...");
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "",
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -738,11 +752,7 @@ const SewagePipeMaintainInp = () => {
|
||||
const baseRequest = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -758,7 +768,7 @@ const SewagePipeMaintainInp = () => {
|
||||
|
||||
const param: SewagePipeMaintainRepairSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildRepairRowList(currentData),
|
||||
};
|
||||
if (isAdminInfo) {
|
||||
@@ -795,7 +805,7 @@ const SewagePipeMaintainInp = () => {
|
||||
|
||||
const param: SewagePipeMaintainDredgingSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildDredgingRowList(currentData),
|
||||
};
|
||||
if (isAdminInfo) {
|
||||
@@ -844,14 +854,10 @@ const SewagePipeMaintainInp = () => {
|
||||
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey;
|
||||
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -880,7 +886,9 @@ const SewagePipeMaintainInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setLastSearchedValues(null);
|
||||
setHasSearched(false);
|
||||
@@ -925,7 +933,9 @@ const SewagePipeMaintainInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
@@ -1000,10 +1010,10 @@ const SewagePipeMaintainInp = () => {
|
||||
for (const tab of SewagePipeMaintainInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = tab.columns ?? [];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1021,10 +1031,9 @@ const SewagePipeMaintainInp = () => {
|
||||
return map;
|
||||
}, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]);
|
||||
|
||||
const searchContextForToolbar = lastSearchedValues ?? searchValues;
|
||||
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
||||
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
||||
const activeTabToolbar =
|
||||
toolbarSnapshotByTab.get(activeTab) ??
|
||||
computeTabToolbarSnapshot(
|
||||
activeTabStatus,
|
||||
tabRowDataExists(rowDataMap, activeTab),
|
||||
@@ -1045,6 +1054,7 @@ const SewagePipeMaintainInp = () => {
|
||||
baseShowEnterableToolbarButtons && toolbarGroupAllowed;
|
||||
const showActionToolbarCard =
|
||||
baseShowActionToolbarCard && toolbarGroupAllowed;
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
|
||||
const openVerificationHistoryDialog = () => {
|
||||
const ctx = lastSearchedValues ?? searchValues;
|
||||
@@ -1061,9 +1071,8 @@ const SewagePipeMaintainInp = () => {
|
||||
const statsCd = tabStatsCdMap.get(activeTab);
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -1099,8 +1108,8 @@ const SewagePipeMaintainInp = () => {
|
||||
return errors
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR" ? "IMR검증 - " : e.vrfcCd === "NV" ? "일반검증 - " : "HB검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
e.vrfcCd === "IMR" ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1111,6 +1120,7 @@ const SewagePipeMaintainInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
@@ -1131,10 +1141,10 @@ const SewagePipeMaintainInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void getLastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
@@ -1166,11 +1176,11 @@ const SewagePipeMaintainInp = () => {
|
||||
const tabKey = info.tabKey;
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
|
||||
return (
|
||||
@@ -1182,6 +1192,7 @@ const SewagePipeMaintainInp = () => {
|
||||
toGridColumnInfo(info)
|
||||
}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
fixedColumns={info.fixedColumns}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
@@ -1206,6 +1217,9 @@ const SewagePipeMaintainInp = () => {
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -1,21 +1,59 @@
|
||||
import {
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
|
||||
import { flushSync } from "react-dom";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { ValueType } from "realgrid";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsInputSewageReservoirPumpApi } from "@/entities/statistics/api/input/statistics-input-sewage-reservoir-pump-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
type RainfallPumpStationItem,
|
||||
type RainfallPumpStationSaveItem,
|
||||
SEWAGE_RESERVOIR_STATS_CD,
|
||||
type SewageReservoirItem,
|
||||
type SewageReservoirRequestParams,
|
||||
type SewageReservoirSaveItem,
|
||||
type SewageReservoirVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-reservoir-pump-interface";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type {
|
||||
ApiResponse,
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sgg,
|
||||
Sido,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
resolveNoApplicableForRequest,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
import AddArticleButton from "@/components/button/AddArticleButton";
|
||||
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
||||
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
|
||||
@@ -25,13 +63,10 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import type {
|
||||
CellStyleCallbackParams,
|
||||
CellTooltipCallbackParams,
|
||||
@@ -39,44 +74,24 @@ import type {
|
||||
ColumnInfo,
|
||||
ColumnInfoList,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { RainfallPumpStationInpColumn } from "@/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn";
|
||||
import { SewageReservoirInpColumn } from "@/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import {
|
||||
formatCompactYmdForGrid,
|
||||
normalizeOxYnValue,
|
||||
toCompactYmdOrNull,
|
||||
} from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputSewageReservoirPumpApi } from "@/entities/statistics/api/input/statistics-input-sewage-reservoir-pump-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import {
|
||||
SEWAGE_RESERVOIR_STATS_CD,
|
||||
type SewageReservoirItem,
|
||||
type SewageReservoirRequestParams,
|
||||
type SewageReservoirSaveItem,
|
||||
type RainfallPumpStationItem,
|
||||
type RainfallPumpStationSaveItem,
|
||||
type SewageReservoirVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-reservoir-pump-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type {
|
||||
ApiResponse,
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
Sgg,
|
||||
SewerageStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
const SewageReservoirPumpInpTabList = [
|
||||
SewageReservoirInpColumn,
|
||||
@@ -92,12 +107,6 @@ const firstTabKey = SewageReservoirPumpInpTabList[0]?.tabKey ?? SEWAGE_RESERVOIR
|
||||
const rainfallPumpTabKey =
|
||||
RainfallPumpStationInpColumn.tabKey ?? "rainfallPumpStationInp";
|
||||
|
||||
const SEWAGE_RESERVOIR_PUMP_COLUMN_GUIDE_TAB_KEYS = Array.from(
|
||||
new Set(
|
||||
SewageReservoirPumpInpTabList.map((tab) => tab.tabKey).filter(Boolean),
|
||||
),
|
||||
);
|
||||
|
||||
const SEWAGE_RESERVOIR_FACILITY_NAME_GUIDE_MESSAGE =
|
||||
"하수저류시설 등록시 하수저류시설명이 동일할 경우 같은 데이터로 분류됩니다.\n\n" +
|
||||
"하수저류시설명이 동일 할 경우 구분을 위해 하수저류시설 1, 하수저류시설 2 형태로 구분하여 주시길 바랍니다.";
|
||||
@@ -116,8 +125,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -125,10 +134,22 @@ const computeTabEnterable = (
|
||||
return periodEnterable;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || "")
|
||||
? Boolean(values?.gugun && values.gugun !== "ALL")
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildNoApplicableForRequest(
|
||||
values: SearchFormValues | null,
|
||||
checked: boolean,
|
||||
isSaveListEmpty: boolean,
|
||||
): boolean {
|
||||
return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty);
|
||||
}
|
||||
|
||||
function computeTabToolbarSnapshot(
|
||||
@@ -141,8 +162,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -170,9 +191,8 @@ function buildSewageReservoirSearchParam(
|
||||
const fcltNm = values.fctName?.trim();
|
||||
return {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
...(fcltNm ? { fcltNm } : {}),
|
||||
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
||||
};
|
||||
@@ -245,15 +265,16 @@ function buildSggNmColumn(
|
||||
function mapSewageReservoirListToGridRows(
|
||||
list: SewageReservoirItem[],
|
||||
ctpvNm: string,
|
||||
values?: SearchFormValues | null,
|
||||
): GridRowWithVerification[] {
|
||||
return list.map((item) => {
|
||||
return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
|
||||
const row = toGridRows([
|
||||
{
|
||||
...item,
|
||||
ctpvNm,
|
||||
oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd),
|
||||
fldngPrvtnYn: normalizeOxYnValue(item.fldngPrvtnYn),
|
||||
wqPlltnMtgtYn: normalizeOxYnValue(item.wqPlltnMtgtYn),
|
||||
wtpltMtgtYn: normalizeOxYnValue(item.wtpltMtgtYn),
|
||||
rtlztnUsgYn: normalizeOxYnValue(item.rtlztnUsgYn),
|
||||
},
|
||||
])[0] as GridRowWithVerification;
|
||||
@@ -264,13 +285,6 @@ function mapSewageReservoirListToGridRows(
|
||||
});
|
||||
}
|
||||
|
||||
function nullableTrimmedString(
|
||||
value: string | number | null | undefined,
|
||||
): string | null {
|
||||
const s = String(value ?? "").trim();
|
||||
return s === "" ? null : s;
|
||||
}
|
||||
|
||||
function toGridNumber(value: string | number | null | undefined): number {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
@@ -290,13 +304,13 @@ function toPumpDmtrExpln(value: string | number | null | undefined): number {
|
||||
function resolveSewageReservoirAdmdst(
|
||||
sggNm: string,
|
||||
sggOptions: Sgg[],
|
||||
): { admdstCd: string; admdstNm: string } {
|
||||
): { admdstCd: string | null; admdstNm: string | null } {
|
||||
const trimmed = sggNm.trim();
|
||||
const matched = sggOptions.find((s) => s.gugun === trimmed);
|
||||
if (matched) {
|
||||
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
|
||||
}
|
||||
return { admdstCd: "", admdstNm: trimmed };
|
||||
return { admdstCd: null, admdstNm: nullableTrimmedString(trimmed) };
|
||||
}
|
||||
|
||||
/** 그리드 행 → ST00040001 저장/삭제 API 항목 */
|
||||
@@ -308,48 +322,47 @@ function buildSewageReservoirSaveItem(
|
||||
String(row.sggNm ?? ""),
|
||||
sggOptions,
|
||||
);
|
||||
const fcltSn = String(row.fcltSn ?? "").trim();
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
// 항목 추가 직후 저장(신규) — 그리드 fcltSn 없음 → API에는 "1"
|
||||
fcltSn: fcltSn,
|
||||
fcltNm: String(row.fcltNm ?? ""),
|
||||
fcltPstnCn: String(row.fcltPstnCn ?? ""),
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
fcltNm: nullableTrimmedString(row.fcltNm),
|
||||
fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
|
||||
oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd),
|
||||
planDesignFreqCnt: toGridNumber(row.planDesignFreqCnt),
|
||||
fldngPrvtnYn: normalizeOxYnValue(row.fldngPrvtnYn),
|
||||
wqPlltnMtgtYn: normalizeOxYnValue(row.wqPlltnMtgtYn),
|
||||
rtlztnUsgYn: normalizeOxYnValue(row.rtlztnUsgYn),
|
||||
fldngPrvtnYn: nullableTrimmedString(normalizeOxYnValue(row.fldngPrvtnYn)),
|
||||
wtpltMtgtYn: nullableTrimmedString(normalizeOxYnValue(row.wtpltMtgtYn)),
|
||||
rtlztnUsgYn: nullableTrimmedString(normalizeOxYnValue(row.rtlztnUsgYn)),
|
||||
fcltStrctCn: nullableTrimmedString(row.fcltStrctCn),
|
||||
wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea),
|
||||
impdFcltCpct: toGridNumber(row.impdFcltCpct),
|
||||
grssPpqty: toGridNumber(row.grssPpqty),
|
||||
wholPpqty: toGridNumber(row.wholPpqty),
|
||||
pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln),
|
||||
pumpFcltN1PerPpqty: toGridNumber(row.pumpFcltN1PerPpqty),
|
||||
pumpFcltN1Ppqty: toGridNumber(row.pumpFcltN1Ppqty),
|
||||
pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt),
|
||||
pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt),
|
||||
pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln),
|
||||
pumpFcltN2PerPpqty: toGridNumber(row.pumpFcltN2PerPpqty),
|
||||
pumpFcltN2Ppqty: toGridNumber(row.pumpFcltN2Ppqty),
|
||||
pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt),
|
||||
pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt),
|
||||
pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln),
|
||||
pumpFcltN3PerPpqty: toGridNumber(row.pumpFcltN3PerPpqty),
|
||||
pumpFcltN3Ppqty: toGridNumber(row.pumpFcltN3Ppqty),
|
||||
pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt),
|
||||
pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt),
|
||||
prcsgFcltYn: String(row.prcsgFcltYn ? (row.prcsgFcltYn === '무' ? 'N' : 'Y') : ""),
|
||||
ddrztnEqpmYn: String(row.ddrztnEqpmYn ? (row.prcsgFcltYn === '무' ? 'N' : 'Y') : ""),
|
||||
oslfEtgtYn: String(row.oslfEtgtYn ? (row.prcsgFcltYn === '무' ? 'N' : 'Y') : ""),
|
||||
mltvMthCn: nullableTrimmedString(row.mltvMthCn),
|
||||
prcsgFcltYn: row.prcsgFcltYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
odorEqpmYn: row.odorEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
oslfEtgtYn: row.oslfEtgtYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
drgptnCn: nullableTrimmedString(row.drgptnCn),
|
||||
impdMthCn: nullableTrimmedString(row.impdMthCn),
|
||||
impdPrcsMthdCn: nullableTrimmedString(row.impdPrcsMthdCn),
|
||||
dschgWtrsExpln: String(row.dschgWtrsExpln ?? ""),
|
||||
prcptnLastDspsQnt: toGridNumber(row.prcptnLastDspsQnt),
|
||||
impdPrcsCn: nullableTrimmedString(row.impdPrcsCn),
|
||||
dschgWtrsExpln: nullableTrimmedString(row.dschgWtrsExpln),
|
||||
prpwLastDspsQnt: toGridNumber(row.prpwLastDspsQnt),
|
||||
ctmnLastDspsQnt: toGridNumber(row.ctmnLastDspsQnt),
|
||||
prcptnLastDspsMthdExpln: String(row.prcptnLastDspsMthdExpln ?? ""),
|
||||
ctmnLastDspsMthdExpln: String(row.ctmnLastDspsMthdExpln ?? ""),
|
||||
prpwLastDspsExpln: nullableTrimmedString(row.prpwLastDspsExpln),
|
||||
ctmnLastDspsExpln: nullableTrimmedString(row.ctmnLastDspsExpln),
|
||||
oprtngDayCnt: toGridNumber(row.oprtngDayCnt),
|
||||
grssMltvQnt: toGridNumber(row.grssMltvQnt),
|
||||
wholQnt: toGridNumber(row.wholQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -363,43 +376,42 @@ function buildRainfallPumpStationSaveItem(
|
||||
String(row.sggNm ?? ""),
|
||||
sggOptions,
|
||||
);
|
||||
const fcltSn = String(row.fcltSn ?? "").trim();
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
fcltSn: fcltSn,
|
||||
fcltNm: String(row.fcltNm ?? ""),
|
||||
fcltPstnCn: String(row.fcltPstnCn ?? ""),
|
||||
fcltyKndCn: String(row.fcltyKndCn ?? ""),
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
fcltNm: nullableTrimmedString(row.fcltNm),
|
||||
fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
|
||||
fcltyKndCn: nullableTrimmedString(row.fcltyKndCn),
|
||||
oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd),
|
||||
planDesignFreqCnt: toGridNumber(row.planDesignFreqCnt),
|
||||
wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea),
|
||||
rsvrYn: String(row.rsvrYn ? (row.rsvrYn === "무" ? "N" : "Y") : ""),
|
||||
rsvrImpndQnt: toGridNumber(row.rsvrImpndQnt),
|
||||
runwtrYn: row.runwtrYn ? (row.runwtrYn === "무" ? "N" : "Y") : null,
|
||||
runwtrStrqty: toGridNumber(row.runwtrStrqty),
|
||||
abstnRgnImpdQnt: toGridNumber(row.abstnRgnImpdQnt),
|
||||
grssPpqty: toGridNumber(row.grssPpqty),
|
||||
wholPpqty: toGridNumber(row.wholPpqty),
|
||||
pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln),
|
||||
pumpFcltN1PerPpqty: toGridNumber(row.pumpFcltN1PerPpqty),
|
||||
pumpFcltN1UnitPpqty: toGridNumber(row.pumpFcltN1UnitPpqty),
|
||||
pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt),
|
||||
pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt),
|
||||
pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln),
|
||||
pumpFcltN2PerPpqty: toGridNumber(row.pumpFcltN2PerPpqty),
|
||||
pumpFcltN2UnitPpqty: toGridNumber(row.pumpFcltN2UnitPpqty),
|
||||
pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt),
|
||||
pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt),
|
||||
pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln),
|
||||
pumpFcltN3PerPpqty: toGridNumber(row.pumpFcltN3PerPpqty),
|
||||
pumpFcltN3UnitPpqty: toGridNumber(row.pumpFcltN3UnitPpqty),
|
||||
pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt),
|
||||
pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt),
|
||||
prcsgFcltYn: String(row.prcsgFcltYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : ""),
|
||||
ddrztnEqpmYn: String(row.ddrztnEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : ""),
|
||||
oslfEtgtYn: String(row.oslfEtgtYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : ""),
|
||||
dschgWtrsExpln: String(row.dschgWtrsExpln ?? ""),
|
||||
prcptnLastDspsQnt: toGridNumber(row.prcptnLastDspsQnt),
|
||||
prcsgFcltYn: row.prcsgFcltYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
odorEqpmYn: row.odorEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
oslfEtgtYn: row.oslfEtgtYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
dschgWtrsExpln: nullableTrimmedString(row.dschgWtrsExpln),
|
||||
prpwLastDspsQnt: toGridNumber(row.prpwLastDspsQnt),
|
||||
ctmnLastDspsQnt: toGridNumber(row.ctmnLastDspsQnt),
|
||||
prcptnLastDspsMthdExpln: String(row.prcptnLastDspsMthdExpln ?? ""),
|
||||
ctmnLastDspsMthdExpln: String(row.ctmnLastDspsMthdExpln ?? ""),
|
||||
prpwLastDspsExpln: nullableTrimmedString(row.prpwLastDspsExpln),
|
||||
ctmnLastDspsExpln: nullableTrimmedString(row.ctmnLastDspsExpln),
|
||||
oprtngDayCnt: toGridNumber(row.oprtngDayCnt),
|
||||
grssMltvQnt: toGridNumber(row.grssMltvQnt),
|
||||
wholQnt: toGridNumber(row.wholQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -407,16 +419,17 @@ function buildRainfallPumpStationSaveItem(
|
||||
function mapRainfallPumpStationListToGridRows(
|
||||
list: RainfallPumpStationItem[],
|
||||
ctpvNm: string,
|
||||
values?: SearchFormValues | null,
|
||||
): GridRowWithVerification[] {
|
||||
return list.map((item) => {
|
||||
return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
|
||||
const row = toGridRows([
|
||||
{
|
||||
...item,
|
||||
ctpvNm,
|
||||
oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd),
|
||||
rsvrYn: normalizeOxYnValue(item.rsvrYn),
|
||||
runwtrYn: normalizeOxYnValue(item.runwtrYn),
|
||||
prcsgFcltYn: normalizeOxYnValue(item.prcsgFcltYn),
|
||||
ddrztnEqpmYn: normalizeOxYnValue(item.ddrztnEqpmYn),
|
||||
odorEqpmYn: normalizeOxYnValue(item.odorEqpmYn),
|
||||
oslfEtgtYn: normalizeOxYnValue(item.oslfEtgtYn),
|
||||
},
|
||||
])[0] as GridRowWithVerification;
|
||||
@@ -446,15 +459,17 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
// 하수도 통계 관리 > 04. 하수저류 시설 및 빗물 펌프장
|
||||
const SewageReservoirPumpInp = () => {
|
||||
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
@@ -486,9 +501,9 @@ const SewageReservoirPumpInp = () => {
|
||||
alert("시군구를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getCurrentRowsForTab(tabKey).length === 0);
|
||||
const currentData = getCurrentRowsForTab(tabKey);
|
||||
if (!noApplicable && currentData.length === 0) {
|
||||
if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -513,14 +528,10 @@ const SewageReservoirPumpInp = () => {
|
||||
const userInfo = await getUserInfo();
|
||||
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -671,7 +682,7 @@ const SewageReservoirPumpInp = () => {
|
||||
if (!(await confirm("저장 하시겠습니까?"))) return;
|
||||
|
||||
commitGridForTab(tabKey);
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getCurrentRowsForTab(tabKey).length === 0);
|
||||
const currentData = getCurrentRowsForTab(tabKey);
|
||||
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
|
||||
const saveList = noApplicable
|
||||
@@ -690,7 +701,7 @@ const SewageReservoirPumpInp = () => {
|
||||
: rainfallPumpStationDeleteList
|
||||
.filter((row) => String(row.fcltSn ?? "").trim() !== "")
|
||||
.map((row) => buildRainfallPumpStationSaveItem(row, sggOptions));
|
||||
if (!noApplicable && saveList.length === 0) {
|
||||
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -698,11 +709,7 @@ const SewageReservoirPumpInp = () => {
|
||||
const requestBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
...(noApplicable ? { noApplicable: true } : {}),
|
||||
};
|
||||
@@ -713,13 +720,13 @@ const SewageReservoirPumpInp = () => {
|
||||
const response = isReservoirTab
|
||||
? await statisticsInputSewageReservoirPumpApi.saveSewageReservoir({
|
||||
...requestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: saveList as SewageReservoirSaveItem[],
|
||||
deleteList: deleteList as SewageReservoirSaveItem[],
|
||||
})
|
||||
: await statisticsInputSewageReservoirPumpApi.saveRainfallPumpStation({
|
||||
...requestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: saveList as RainfallPumpStationSaveItem[],
|
||||
deleteList: deleteList as RainfallPumpStationSaveItem[],
|
||||
});
|
||||
@@ -762,7 +769,7 @@ const SewageReservoirPumpInp = () => {
|
||||
}
|
||||
|
||||
commitGridForTab(tabKey);
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getCurrentRowsForTab(tabKey).length === 0);
|
||||
const currentData = getCurrentRowsForTab(tabKey);
|
||||
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
|
||||
const submitList = noApplicable
|
||||
@@ -781,12 +788,8 @@ const SewageReservoirPumpInp = () => {
|
||||
const activeTabStatus = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey;
|
||||
const verificationQuery = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
@@ -800,7 +803,7 @@ const SewageReservoirPumpInp = () => {
|
||||
await loadActiveTabData(tabKey, lastSearchedValues, true, false);
|
||||
};
|
||||
|
||||
if (!noApplicable && submitList.length === 0) {
|
||||
if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -808,20 +811,16 @@ const SewageReservoirPumpInp = () => {
|
||||
const submitRequestBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
...(noApplicable ? { noApplicable: true } : {}),
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -831,13 +830,13 @@ const SewageReservoirPumpInp = () => {
|
||||
const response = isReservoirTab
|
||||
? await statisticsInputSewageReservoirPumpApi.submitSewageReservoir({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as SewageReservoirSaveItem[],
|
||||
deleteList: deleteList as SewageReservoirSaveItem[],
|
||||
})
|
||||
: await statisticsInputSewageReservoirPumpApi.submitRainfallPumpStation({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as RainfallPumpStationSaveItem[],
|
||||
deleteList: deleteList as RainfallPumpStationSaveItem[],
|
||||
});
|
||||
@@ -852,15 +851,16 @@ const SewageReservoirPumpInp = () => {
|
||||
|
||||
if (finalStatus === "5") {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel("IMR 검증 진행 중...");
|
||||
setGlobalProgressLabel("시계열 검증 진행 중...");
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "",
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -934,13 +934,19 @@ const SewageReservoirPumpInp = () => {
|
||||
const [tabStatusInfoMap, setTabStatusInfoMap] = useState<
|
||||
Map<string, SewerageStatusInfo | null>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [verificationResultDialogOpen, setVerificationResultDialogOpen] =
|
||||
useState(false);
|
||||
@@ -957,35 +963,29 @@ const SewageReservoirPumpInp = () => {
|
||||
useRef<(() => Record<string, string | number>[]) | null>(null);
|
||||
const sggOptionsSidoRef = useRef<string | null>(null);
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_RESERVOIR_PUMP) return;
|
||||
const tabKey = activeTab;
|
||||
if (!tabKey) return;
|
||||
if (Object.prototype.hasOwnProperty.call(columnGuidesByTab, tabKey)) return;
|
||||
const statsCd = tabKey;
|
||||
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
SEWAGE_RESERVOIR_PUMP_COLUMN_GUIDE_TAB_KEYS.map(async (tabKey) => {
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(tabKey);
|
||||
if (res.status !== "success" || !Array.isArray(res.data)) {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [tabKey, next] as const;
|
||||
} catch {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
const next = await fetchColumnGuidesRecord(statsCd);
|
||||
if (cancelled) return;
|
||||
setColumnGuidesByTab(Object.fromEntries(entries));
|
||||
setColumnGuidesByTab((prev) =>
|
||||
Object.prototype.hasOwnProperty.call(prev, tabKey)
|
||||
? prev
|
||||
: { ...prev, [tabKey]: next },
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [activeTabId, activeTab, columnGuidesByTab]);
|
||||
|
||||
const searchDoneRef = useRef(false);
|
||||
const periodEnterableRef = useRef(true);
|
||||
@@ -1035,6 +1035,13 @@ const SewageReservoirPumpInp = () => {
|
||||
);
|
||||
return next;
|
||||
});
|
||||
if (searchContext) {
|
||||
setSearchContextByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, searchContext);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const applyRowMap = (
|
||||
@@ -1079,6 +1086,7 @@ const SewageReservoirPumpInp = () => {
|
||||
const gridRows = mapSewageReservoirListToGridRows(
|
||||
list,
|
||||
commonInfo.ctpvNm,
|
||||
values,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -1089,6 +1097,9 @@ const SewageReservoirPumpInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(values);
|
||||
}
|
||||
@@ -1124,6 +1135,7 @@ const SewageReservoirPumpInp = () => {
|
||||
const gridRows = mapRainfallPumpStationListToGridRows(
|
||||
list,
|
||||
commonInfo.ctpvNm,
|
||||
values,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -1134,6 +1146,9 @@ const SewageReservoirPumpInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(values);
|
||||
}
|
||||
@@ -1201,7 +1216,9 @@ const SewageReservoirPumpInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
setLastSearchedValues(null);
|
||||
setSggOptions([]);
|
||||
sggOptionsSidoRef.current = null;
|
||||
@@ -1251,7 +1268,9 @@ const SewageReservoirPumpInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
loadedTabKeysRef.current.clear();
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
@@ -1321,10 +1340,9 @@ const SewageReservoirPumpInp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const searchContextForToolbar = lastSearchedValues ?? searchValues;
|
||||
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
||||
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
||||
const activeTabToolbar =
|
||||
toolbarSnapshotByTab.get(activeTab) ??
|
||||
computeTabToolbarSnapshot(
|
||||
activeTabStatus,
|
||||
tabRowDataExists(rowDataMap, activeTab),
|
||||
@@ -1345,6 +1363,7 @@ const SewageReservoirPumpInp = () => {
|
||||
baseShowEnterableToolbarButtons && toolbarGroupAllowed;
|
||||
const showActionToolbarCard =
|
||||
baseShowActionToolbarCard && toolbarGroupAllowed;
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
const isReservoirOrRainfallTab =
|
||||
activeTab === SEWAGE_RESERVOIR_STATS_CD || activeTab === rainfallPumpTabKey;
|
||||
const showArticleToolbarCard =
|
||||
@@ -1359,7 +1378,8 @@ const SewageReservoirPumpInp = () => {
|
||||
activeTab,
|
||||
tabRowDataExists(rowDataMap, activeTab),
|
||||
tabStatusInfoMap.get(activeTab),
|
||||
);
|
||||
) &&
|
||||
submitOk;
|
||||
const setIsNotApplicableForActiveTab: Dispatch<SetStateAction<boolean>> = useCallback(
|
||||
(next) => {
|
||||
const tabKey = activeTabRef.current;
|
||||
@@ -1438,10 +1458,10 @@ const SewageReservoirPumpInp = () => {
|
||||
for (const tab of SewageReservoirPumpInpTabList) {
|
||||
const tabKey = tab.tabKey ?? "";
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
|
||||
const isRainfallPumpTab = tabKey === rainfallPumpTabKey;
|
||||
const rawColumns = tab.columns ?? [];
|
||||
@@ -1500,12 +1520,12 @@ const SewageReservoirPumpInp = () => {
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR"
|
||||
? "IMR검증 - "
|
||||
? "시계열 검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "HB검증 -";
|
||||
: "모집단 검증 -";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1517,9 +1537,8 @@ const SewageReservoirPumpInp = () => {
|
||||
const statsCd = tabStatsCdMap.get(activeTab);
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -1540,6 +1559,7 @@ const SewageReservoirPumpInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName={true}
|
||||
@@ -1561,10 +1581,10 @@ const SewageReservoirPumpInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void getLastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
@@ -1606,11 +1626,11 @@ const SewageReservoirPumpInp = () => {
|
||||
{SewageReservoirPumpInpTabList.map((tab) => {
|
||||
const tabKey = tab.tabKey ?? "";
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
|
||||
return (
|
||||
@@ -1668,6 +1688,9 @@ const SewageReservoirPumpInp = () => {
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore } from "@/shared/lib";
|
||||
import { useAlertStore, useGlobalProgressStore, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
@@ -35,7 +35,15 @@ import type {
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sido,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
@@ -48,21 +56,27 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { CauserBurdenInpColumn } from "@/components/util/column/input/sewerageFinance/CauserBurdenInpColumn";
|
||||
import { SewerageChargeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageChargeInpColumn";
|
||||
import { SewerageFinanceSettleInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageFinanceSettleInpColumn";
|
||||
import { SewerageUsageFeeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageUsageFeeInpColumn";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
const SewerageFinanceInpTabList = [
|
||||
SewerageFinanceSettleInpColumn,
|
||||
@@ -73,10 +87,6 @@ const SewerageFinanceInpTabList = [
|
||||
|
||||
const firstTabKey = SewerageFinanceSettleInpColumn.tabKey;
|
||||
|
||||
const SEWERAGE_FINANCE_COLUMN_GUIDE_TAB_KEYS = Array.from(
|
||||
new Set(SewerageFinanceInpTabList.map((tab) => tab.tabKey).filter(Boolean)),
|
||||
);
|
||||
|
||||
type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: SewerageFinanceVerificationResult;
|
||||
};
|
||||
@@ -98,9 +108,8 @@ function buildSewerageFinanceSearchParam(
|
||||
): SewerageFinanceRequestParams {
|
||||
return {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
||||
};
|
||||
}
|
||||
@@ -109,8 +118,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -118,10 +127,14 @@ const computeTabEnterable = (
|
||||
return periodEnterable;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || "")
|
||||
? Boolean(values?.gugun && values.gugun !== "ALL")
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function computeTabToolbarSnapshot(
|
||||
@@ -134,8 +147,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -157,8 +170,8 @@ function tabRowDataExists(
|
||||
|
||||
function mapRegionalListToGridRows<
|
||||
T extends { verificationResult?: SewerageFinanceVerificationResult },
|
||||
>(list: T[], ctpvNm: string): GridRowWithVerification[] {
|
||||
return list.map((item) => {
|
||||
>(list: T[], ctpvNm: string, values?: SearchFormValues | null): GridRowWithVerification[] {
|
||||
return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
|
||||
const { verificationResult, ...rest } = item;
|
||||
const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification;
|
||||
if (verificationResult) row.verificationResult = verificationResult;
|
||||
@@ -166,17 +179,6 @@ function mapRegionalListToGridRows<
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeGridString(value: unknown): string {
|
||||
if (value === null || value === undefined) return "";
|
||||
return typeof value === "string" ? value : String(value);
|
||||
}
|
||||
|
||||
function normalizeGridRmrk(value: unknown): string | null {
|
||||
if (value === null || value === undefined) return null;
|
||||
const s = typeof value === "string" ? value : String(value);
|
||||
return s.trim() === "" ? null : s;
|
||||
}
|
||||
|
||||
function buildColumnFormatNumber(columns: { fieldName?: string; dataFormat?: string }[]) {
|
||||
const columnFormatMap = new Map<string, string>();
|
||||
columns.forEach((col) => {
|
||||
@@ -212,35 +214,35 @@ function buildSewerageFinanceSettleSaveList(
|
||||
const formatNumber = buildColumnFormatNumber(SewerageFinanceSettleInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
sggNm: normalizeGridString(d.sggNm),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
cnterGovSprtAmt: formatNumber(d.cnterGovSprtAmt, "cnterGovSprtAmt"),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
soaGovSprtAmt: formatNumber(d.soaGovSprtAmt, "soaGovSprtAmt"),
|
||||
mctpvLcexpAmt: formatNumber(d.mctpvLcexpAmt, "mctpvLcexpAmt"),
|
||||
wareaSggLclAmt: formatNumber(d.wareaSggLclAmt, "wareaSggLclAmt"),
|
||||
sewerUseAmt: formatNumber(d.sewerUseAmt, "sewerUseAmt"),
|
||||
orgprBrdnAmt: formatNumber(d.orgprBrdnAmt, "orgprBrdnAmt"),
|
||||
fnnOrgprBrdnAmt: formatNumber(d.fnnOrgprBrdnAmt, "fnnOrgprBrdnAmt"),
|
||||
etcIncmAmt: formatNumber(d.etcIncmAmt, "etcIncmAmt"),
|
||||
crfwAmt: formatNumber(d.crfwAmt, "crfwAmt"),
|
||||
lbltAmt: formatNumber(d.lbltAmt, "lbltAmt"),
|
||||
soaCrfwAmt: formatNumber(d.soaCrfwAmt, "soaCrfwAmt"),
|
||||
soaLbltAmt: formatNumber(d.soaLbltAmt, "soaLbltAmt"),
|
||||
tmplntFcltAmt: formatNumber(d.tmplntFcltAmt, "tmplntFcltAmt"),
|
||||
tmplntMendngAmt: formatNumber(d.tmplntMendngAmt, "tmplntMendngAmt"),
|
||||
tmplntAmt: formatNumber(d.tmplntAmt, "tmplntAmt"),
|
||||
tmplntOperKeepAmt: formatNumber(d.tmplntOperKeepAmt, "tmplntOperKeepAmt"),
|
||||
tmplntEtcAmt: formatNumber(d.tmplntEtcAmt, "tmplntEtcAmt"),
|
||||
swagePplFcltAmt: formatNumber(d.swagePplFcltAmt, "swagePplFcltAmt"),
|
||||
swagePplMendngAmt: formatNumber(d.swagePplMendngAmt, "swagePplMendngAmt"),
|
||||
swagePplAmt: formatNumber(d.swagePplAmt, "swagePplAmt"),
|
||||
swagePplOperKeepAmt: formatNumber(d.swagePplOperKeepAmt, "swagePplOperKeepAmt"),
|
||||
swagePplEtcAmt: formatNumber(d.swagePplEtcAmt, "swagePplEtcAmt"),
|
||||
swageImpdFcltAmt: formatNumber(d.swageImpdFcltAmt, "swageImpdFcltAmt"),
|
||||
swageImpdMendngAmt: formatNumber(d.swageImpdMendngAmt, "swageImpdMendngAmt"),
|
||||
swageImpdAmt: formatNumber(d.swageImpdAmt, "swageImpdAmt"),
|
||||
swageImpdOperKeepAmt: formatNumber(d.swageImpdOperKeepAmt, "swageImpdOperKeepAmt"),
|
||||
swageImpdFcltEtcAmt: formatNumber(d.swageImpdFcltEtcAmt, "swageImpdFcltEtcAmt"),
|
||||
etcAcdntCrfwAmt: formatNumber(d.etcAcdntCrfwAmt, "etcAcdntCrfwAmt"),
|
||||
etcNrcpAmt: formatNumber(d.etcNrcpAmt, "etcNrcpAmt"),
|
||||
etcAmt: formatNumber(d.etcAmt, "etcAmt"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
fnnByWholPjtcoAmt: formatNumber(d.fnnByWholPjtcoAmt, "fnnByWholPjtcoAmt"),
|
||||
usgByWholPjtcoAmt: formatNumber(d.usgByWholPjtcoAmt, "usgByWholPjtcoAmt"),
|
||||
fnnEtcAmt: formatNumber(d.fnnEtcAmt, "fnnEtcAmt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
fnnSeWholPjtcoAmt: formatNumber(d.fnnSeWholPjtcoAmt, "fnnSeWholPjtcoAmt"),
|
||||
usgSeWholPjtcoAmt: formatNumber(d.usgSeWholPjtcoAmt, "usgSeWholPjtcoAmt"),
|
||||
tmplntWholAmt: formatNumber(d.tmplntWholAmt, "tmplntWholAmt"),
|
||||
wholSwagePplAmt: formatNumber(d.wholSwagePplAmt, "wholSwagePplAmt"),
|
||||
wholSwageImpdFcltAmt: formatNumber(d.wholSwageImpdFcltAmt, "wholSwageImpdFcltAmt"),
|
||||
@@ -254,16 +256,16 @@ function buildSewerageChargeSaveList(
|
||||
const formatNumber = buildColumnFormatNumber(SewerageChargeInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
sggNm: normalizeGridString(d.sggNm),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
anlLevyQnt: formatNumber(d.anlLevyQnt, "anlLevyQnt"),
|
||||
levyAmt: formatNumber(d.levyAmt, "levyAmt"),
|
||||
grssPrmcoAmt: formatNumber(d.grssPrmcoAmt, "grssPrmcoAmt"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
sewerLevyAmt: formatNumber(d.sewerLevyAmt, "sewerLevyAmt"),
|
||||
wholPrmcoAmt: formatNumber(d.wholPrmcoAmt, "wholPrmcoAmt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
avgUntprcAmt: formatNumber(d.avgUntprcAmt, "avgUntprcAmt"),
|
||||
grssUnitPrmcoAmt: formatNumber(d.grssUnitPrmcoAmt, "grssUnitPrmcoAmt"),
|
||||
rlztnRt: formatNumber(d.rlztnRt, "rlztnRt"),
|
||||
wholUnitPrmcoAmt: formatNumber(d.wholUnitPrmcoAmt, "wholUnitPrmcoAmt"),
|
||||
sewerCrgRlztnRt: formatNumber(d.sewerCrgRlztnRt, "sewerCrgRlztnRt"),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -273,33 +275,33 @@ function buildSewerageUsageFeeSaveList(
|
||||
const formatNumber = buildColumnFormatNumber(SewerageUsageFeeInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
sggNm: normalizeGridString(d.sggNm),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
hshusLevyAmt: formatNumber(d.hshusLevyAmt, "hshusLevyAmt"),
|
||||
hshusLevyQnt: formatNumber(d.hshusLevyQnt, "hshusLevyQnt"),
|
||||
hshusUntprcAmt: formatNumber(d.hshusUntprcAmt, "hshusUntprcAmt"),
|
||||
taskFrLevyAmt: formatNumber(d.taskFrLevyAmt, "taskFrLevyAmt"),
|
||||
taskFrLevyQnt: formatNumber(d.taskFrLevyQnt, "taskFrLevyQnt"),
|
||||
taskFrUntprcAmt: formatNumber(d.taskFrUntprcAmt, "taskFrUntprcAmt"),
|
||||
ofuseLevyAmt: formatNumber(d.ofuseLevyAmt, "ofuseLevyAmt"),
|
||||
ofuseLevyQnt: formatNumber(d.ofuseLevyQnt, "ofuseLevyQnt"),
|
||||
ofuseUntprcAmt: formatNumber(d.ofuseUntprcAmt, "ofuseUntprcAmt"),
|
||||
fbuLevyAmt: formatNumber(d.fbuLevyAmt, "fbuLevyAmt"),
|
||||
fbuLevyQnt: formatNumber(d.fbuLevyQnt, "fbuLevyQnt"),
|
||||
fbuUntprcAmt: formatNumber(d.fbuUntprcAmt, "fbuUntprcAmt"),
|
||||
bthhsFrSpcs1LevyAmt: formatNumber(d.bthhsFrSpcs1LevyAmt, "bthhsFrSpcs1LevyAmt"),
|
||||
bthhsFrSpcs1LevyQnt: formatNumber(d.bthhsFrSpcs1LevyQnt, "bthhsFrSpcs1LevyQnt"),
|
||||
bthhsFrSpcs1UntprcAmt: formatNumber(d.bthhsFrSpcs1UntprcAmt, "bthhsFrSpcs1UntprcAmt"),
|
||||
bthhsFrSpcs2LevyAmt: formatNumber(d.bthhsFrSpcs2LevyAmt, "bthhsFrSpcs2LevyAmt"),
|
||||
bthhsFrSpcs2LevyQnt: formatNumber(d.bthhsFrSpcs2LevyQnt, "bthhsFrSpcs2LevyQnt"),
|
||||
bthhsFrSpcs2UntprcAmt: formatNumber(d.bthhsFrSpcs2UntprcAmt, "bthhsFrSpcs2UntprcAmt"),
|
||||
induLevyAmt: formatNumber(d.induLevyAmt, "induLevyAmt"),
|
||||
induLevyQnt: formatNumber(d.induLevyQnt, "induLevyQnt"),
|
||||
induUntprcAmt: formatNumber(d.induUntprcAmt, "induUntprcAmt"),
|
||||
btht1LevyAmt: formatNumber(d.btht1LevyAmt, "btht1LevyAmt"),
|
||||
btht1LevyQnt: formatNumber(d.btht1LevyQnt, "btht1LevyQnt"),
|
||||
btht1UntprcAmt: formatNumber(d.btht1UntprcAmt, "btht1UntprcAmt"),
|
||||
btht2LevyAmt: formatNumber(d.btht2LevyAmt, "btht2LevyAmt"),
|
||||
btht2LevyQnt: formatNumber(d.btht2LevyQnt, "btht2LevyQnt"),
|
||||
btht2UntprcAmt: formatNumber(d.btht2UntprcAmt, "btht2UntprcAmt"),
|
||||
indstLevyAmt: formatNumber(d.indstLevyAmt, "indstLevyAmt"),
|
||||
indstLevyQnt: formatNumber(d.indstLevyQnt, "indstLevyQnt"),
|
||||
indstUntprcAmt: formatNumber(d.indstUntprcAmt, "indstUntprcAmt"),
|
||||
etcLevyAmt: formatNumber(d.etcLevyAmt, "etcLevyAmt"),
|
||||
etcLevyQnt: formatNumber(d.etcLevyQnt, "etcLevyQnt"),
|
||||
etcLevyUntprcAmt: formatNumber(d.etcLevyUntprcAmt, "etcLevyUntprcAmt"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
levyAmt: formatNumber(d.levyAmt, "levyAmt"),
|
||||
levyQnt: formatNumber(d.levyQnt, "levyQnt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
sewerLevyAmt: formatNumber(d.sewerLevyAmt, "sewerLevyAmt"),
|
||||
sewerLevyQnt: formatNumber(d.sewerLevyQnt, "sewerLevyQnt"),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -309,17 +311,17 @@ function buildCauserBurdenSaveList(
|
||||
const formatNumber = buildColumnFormatNumber(CauserBurdenInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
sggNm: normalizeGridString(d.sggNm),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
levyEvntcsCnt: formatNumber(d.levyEvntcsCnt, "levyEvntcsCnt"),
|
||||
levyAmt: formatNumber(d.levyAmt, "levyAmt"),
|
||||
mltvEqpmEvntcsCnt: formatNumber(d.mltvEqpmEvntcsCnt, "mltvEqpmEvntcsCnt"),
|
||||
mltvEqpmAmt: formatNumber(d.mltvEqpmAmt, "mltvEqpmAmt"),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
brdnAmtLevyNocs: formatNumber(d.brdnAmtLevyNocs, "brdnAmtLevyNocs"),
|
||||
brdnAmtLevyAmt: formatNumber(d.brdnAmtLevyAmt, "brdnAmtLevyAmt"),
|
||||
drgNocs: formatNumber(d.drgNocs, "drgNocs"),
|
||||
drgAmt: formatNumber(d.drgAmt, "drgAmt"),
|
||||
othrCstrnOthrActrCnt: formatNumber(d.othrCstrnOthrActrCnt, "othrCstrnOthrActrCnt"),
|
||||
othrCstrnOthrActrAmt: formatNumber(d.othrCstrnOthrActrAmt, "othrCstrnOthrActrAmt"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
trgtByWholClctnAmt: formatNumber(d.trgtByWholClctnAmt, "trgtByWholClctnAmt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
trgtSeWholClctnAmt: formatNumber(d.trgtSeWholClctnAmt, "trgtSeWholClctnAmt"),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -341,15 +343,17 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
// 하수도 통계 관리 > 09. 하수도재정
|
||||
const SewerageFinanceInp = () => {
|
||||
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
@@ -374,9 +378,15 @@ const SewerageFinanceInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [columnGuidesByTab, setColumnGuidesByTab] = useState<
|
||||
Record<string, Record<string, string>>
|
||||
>({});
|
||||
@@ -391,35 +401,29 @@ const SewerageFinanceInp = () => {
|
||||
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
|
||||
activeTabRef.current = activeTab;
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_FINANCE) return;
|
||||
const tabKey = activeTab;
|
||||
if (!tabKey) return;
|
||||
if (Object.prototype.hasOwnProperty.call(columnGuidesByTab, tabKey)) return;
|
||||
const statsCd = tabKey;
|
||||
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
SEWERAGE_FINANCE_COLUMN_GUIDE_TAB_KEYS.map(async (tabKey) => {
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(tabKey);
|
||||
if (res.status !== "success" || !Array.isArray(res.data)) {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [tabKey, next] as const;
|
||||
} catch {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
const next = await fetchColumnGuidesRecord(statsCd);
|
||||
if (cancelled) return;
|
||||
setColumnGuidesByTab(Object.fromEntries(entries));
|
||||
setColumnGuidesByTab((prev) =>
|
||||
Object.prototype.hasOwnProperty.call(prev, tabKey)
|
||||
? prev
|
||||
: { ...prev, [tabKey]: next },
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [activeTabId, activeTab, columnGuidesByTab]);
|
||||
|
||||
const commitToolbarSnapshotForLoadedTab = (
|
||||
tabKey: string,
|
||||
@@ -442,6 +446,13 @@ const SewerageFinanceInp = () => {
|
||||
);
|
||||
return next;
|
||||
});
|
||||
if (searchContext) {
|
||||
setSearchContextByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, searchContext);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const applyRowMap = (
|
||||
@@ -482,6 +493,7 @@ const SewerageFinanceInp = () => {
|
||||
const gridRows = mapRegionalListToGridRows(
|
||||
(sewerageFinanceSettleList ?? []) as SewerageFinanceSettleItem[],
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -492,6 +504,9 @@ const SewerageFinanceInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -508,6 +523,7 @@ const SewerageFinanceInp = () => {
|
||||
const gridRows = mapRegionalListToGridRows(
|
||||
(sewerageChargeList ?? []) as SewerageChargeItem[],
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -518,6 +534,9 @@ const SewerageFinanceInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -534,6 +553,7 @@ const SewerageFinanceInp = () => {
|
||||
const gridRows = mapRegionalListToGridRows(
|
||||
(sewerageUsageFeeList ?? []) as SewerageUsageFeeItem[],
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -544,6 +564,9 @@ const SewerageFinanceInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -560,6 +583,7 @@ const SewerageFinanceInp = () => {
|
||||
const gridRows = mapRegionalListToGridRows(
|
||||
(causerBurdenList ?? []) as CauserBurdenItem[],
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -570,6 +594,9 @@ const SewerageFinanceInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -587,7 +614,9 @@ const SewerageFinanceInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
setLastSearchedValues(null);
|
||||
@@ -629,7 +658,9 @@ const SewerageFinanceInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
@@ -739,14 +770,10 @@ const SewerageFinanceInp = () => {
|
||||
}
|
||||
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -827,28 +854,20 @@ const SewerageFinanceInp = () => {
|
||||
const submitInfoBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
};
|
||||
|
||||
const verificationQuery = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
@@ -873,7 +892,7 @@ const SewerageFinanceInp = () => {
|
||||
if (isSettleTab) {
|
||||
const param: SewerageFinanceSettleSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSewerageFinanceSettleSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
@@ -884,7 +903,7 @@ const SewerageFinanceInp = () => {
|
||||
} else if (isChargeTab) {
|
||||
const param: SewerageChargeSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSewerageChargeSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
@@ -892,7 +911,7 @@ const SewerageFinanceInp = () => {
|
||||
} else if (isUsageFeeTab) {
|
||||
const param: SewerageUsageFeeSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSewerageUsageFeeSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
@@ -901,7 +920,7 @@ const SewerageFinanceInp = () => {
|
||||
} else {
|
||||
const param: CauserBurdenSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildCauserBurdenSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
@@ -918,14 +937,15 @@ const SewerageFinanceInp = () => {
|
||||
|
||||
if (finalStatus === "5") {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel("IMR 검증 진행 중...");
|
||||
setGlobalProgressLabel("시계열 검증 진행 중...");
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "",
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1009,11 +1029,7 @@ const SewerageFinanceInp = () => {
|
||||
const baseRequest = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1023,7 +1039,7 @@ const SewerageFinanceInp = () => {
|
||||
if (isSettleTab) {
|
||||
const param: SewerageFinanceSettleSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSewerageFinanceSettleSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1047,7 +1063,7 @@ const SewerageFinanceInp = () => {
|
||||
if (isChargeTab) {
|
||||
const param: SewerageChargeSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSewerageChargeSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1071,7 +1087,7 @@ const SewerageFinanceInp = () => {
|
||||
if (isUsageFeeTab) {
|
||||
const param: SewerageUsageFeeSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSewerageUsageFeeSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1094,7 +1110,7 @@ const SewerageFinanceInp = () => {
|
||||
|
||||
const param: CauserBurdenSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildCauserBurdenSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1134,9 +1150,8 @@ const SewerageFinanceInp = () => {
|
||||
const statsCd = tabStatsCdMap.get(activeTab);
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -1147,10 +1162,10 @@ const SewerageFinanceInp = () => {
|
||||
for (const tab of SewerageFinanceInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1167,19 +1182,20 @@ const SewerageFinanceInp = () => {
|
||||
return map;
|
||||
}, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]);
|
||||
|
||||
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
||||
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
||||
const activeTabToolbar =
|
||||
toolbarSnapshotByTab.get(activeTab) ??
|
||||
computeTabToolbarSnapshot(
|
||||
activeTabStatus,
|
||||
tabRowDataExists(rowDataMap, activeTab),
|
||||
hasSearched,
|
||||
lastSearchedValues,
|
||||
searchContextForToolbar,
|
||||
inputPeriodEnterable,
|
||||
isAdminInfo,
|
||||
);
|
||||
const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } =
|
||||
activeTabToolbar;
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
|
||||
const verificationCellStyle = ({
|
||||
fieldName,
|
||||
@@ -1212,11 +1228,11 @@ const SewerageFinanceInp = () => {
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR"
|
||||
? "IMR검증 - "
|
||||
? "시계열 검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "HB검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1238,6 +1254,7 @@ const SewerageFinanceInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
@@ -1245,8 +1262,8 @@ const SewerageFinanceInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
@@ -1258,10 +1275,10 @@ const SewerageFinanceInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void lastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
@@ -1293,11 +1310,11 @@ const SewerageFinanceInp = () => {
|
||||
const tabKey = info.tabKey;
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
|
||||
return (
|
||||
@@ -1335,6 +1352,9 @@ const SewerageFinanceInp = () => {
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { useAlertStore, useGlobalProgressStore, useSearchConditionSidoList, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import {
|
||||
STATISTICS_INPUT_URL_PATH,
|
||||
verificationSearchToFormValues,
|
||||
@@ -25,8 +25,15 @@ import type {
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sido,
|
||||
SubmitExceptionRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
@@ -40,17 +47,18 @@ import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
// import { DataGrid } from "@rsone/components";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -60,6 +68,28 @@ type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: SewerageSupplyRateVerificationResult;
|
||||
};
|
||||
|
||||
/** commonInfo 푸터 필드명 → 그리드 fieldName */
|
||||
const FOOTER_VERIFY_FIELD_ALIASES: Record<string, string> = {
|
||||
sewerBzmnWholPpltnCnt: "pbadmsWholPpltnCnt",
|
||||
sewerBzmnDomplWholPpltnCnt: "domplWholPpltnCnt",
|
||||
sewerBzmnFrgnrWholPpltnCnt: "frgnrWholPpltnCnt",
|
||||
sewerBzmnArea: "refArea",
|
||||
};
|
||||
|
||||
const remapCommonInfoVerificationToGridFields = (
|
||||
result: SewerageSupplyRateVerificationResult | null | undefined,
|
||||
): VerificationResultLikeMap | null => {
|
||||
if (!result) return null;
|
||||
const remapped: VerificationResultLikeMap = {};
|
||||
for (const [key, errors] of Object.entries(result)) {
|
||||
const gridField = FOOTER_VERIFY_FIELD_ALIASES[key] ?? key;
|
||||
remapped[gridField] = errors;
|
||||
// API 키로도 조회 가능하도록 유지
|
||||
if (gridField !== key) remapped[key] = errors;
|
||||
}
|
||||
return remapped;
|
||||
};
|
||||
|
||||
/** 컬럼 가이드 API 조회용 통계코드 (하수도 보급률) */
|
||||
const SEWERAGE_PENETRATION_COLUMN_GUIDE_STATS_CD = "ST00010001";
|
||||
|
||||
@@ -87,36 +117,31 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function getSewerageSupplyRateParams(
|
||||
values: SearchFormValues,
|
||||
overrides?: { yr?: string; demoYr?: string; mode?: string }
|
||||
): SewerageSupplyRateRequestParams {
|
||||
return {
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
statsYr: overrides?.yr ?? values.year,
|
||||
...(overrides?.mode && { mode: overrides.mode }),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeGridString(value: unknown): string | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.trim() === "" ? null : value;
|
||||
}
|
||||
|
||||
// 하수도 통계 관리 > 01. 하수도 보급률
|
||||
const SeweragePenetrationRateInp = () => {
|
||||
const { sidoList, isReady } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
@@ -137,6 +162,8 @@ const SeweragePenetrationRateInp = () => {
|
||||
/** `engColNm` → `columnGuide` (헤더 hover) */
|
||||
const [columnGuidesByEngCol, setColumnGuidesByEngCol] = useState<Record<string, string>>({});
|
||||
const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null);
|
||||
const [footerVerificationResult, setFooterVerificationResult] =
|
||||
useState<VerificationResultLikeMap | null>(null);
|
||||
const { confirm } = useConfirm();
|
||||
const showGlobalProgress = useGlobalProgressStore((state) => state.show);
|
||||
const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress);
|
||||
@@ -156,15 +183,16 @@ const SeweragePenetrationRateInp = () => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE) {
|
||||
return;
|
||||
}
|
||||
if (!isReady) return;
|
||||
const pending = useStatisticsInputFocusStore
|
||||
.getState()
|
||||
.takePendingForUrl(STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE);
|
||||
if (!pending?.search) return;
|
||||
const values = verificationSearchToFormValues(pending.search);
|
||||
const values = verificationSearchToFormValues(pending.search, sidoList);
|
||||
setVerificationSearchDefaults(values);
|
||||
postVerificationSearchRef.current = values;
|
||||
setSearchFormRemountKey((k) => k + 1);
|
||||
}, [activeTabId, inputFocusNonce]);
|
||||
}, [activeTabId, inputFocusNonce, isReady, sidoList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE) {
|
||||
@@ -208,11 +236,14 @@ const SeweragePenetrationRateInp = () => {
|
||||
(footerFromApi && footerFromApi[fieldName] != null ? footerFromApi[fieldName] : null) ?? getFooterSummary(grid, column),
|
||||
});
|
||||
|
||||
// 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요.
|
||||
|
||||
// 관리자는 제출/승인 상태에서도 편집 가능. 단, 입력기간 외에는 불가.
|
||||
const columnInfo = useMemo(() => {
|
||||
const { columns, columnLayout } = SeweragePenetrationRateInpColumn;
|
||||
const base = (isEnterable || isAdminInfo) ? columns : columns.map((col) => ({ ...col, editable: false as const }));
|
||||
const canEdit =
|
||||
(isEnterable || isAdminInfo) && isInputPeriodCurrent(periodCheckList);
|
||||
const base = canEdit
|
||||
? columns
|
||||
: columns.map((col) => ({ ...col, editable: false as const }));
|
||||
|
||||
const withHeaderTooltips = base.map((col) => {
|
||||
const guide = columnGuidesByEngCol[col.fieldName]?.trim();
|
||||
@@ -233,7 +264,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
return col;
|
||||
}),
|
||||
};
|
||||
}, [isEnterable, isAdminInfo, footerFromApi, columnGuidesByEngCol]);
|
||||
}, [isEnterable, isAdminInfo, periodCheckList, footerFromApi, columnGuidesByEngCol]);
|
||||
|
||||
const handleReset = () => {
|
||||
setRowData([]);
|
||||
@@ -242,19 +273,28 @@ const SeweragePenetrationRateInp = () => {
|
||||
setLastSearchedYear(null);
|
||||
setLastSearchedValues(null);
|
||||
setFooterFromApi(null);
|
||||
setFooterVerificationResult(null);
|
||||
setVerificationSearchDefaults(null);
|
||||
setIsVerificationFailed(false);
|
||||
setSearchFormRemountKey((k) => k + 1);
|
||||
};
|
||||
|
||||
const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams, isEnterable: boolean) => {
|
||||
const fetchSewerageSupplyRate = async (
|
||||
params: SewerageSupplyRateRequestParams,
|
||||
isEnterable: boolean,
|
||||
values?: SearchFormValues | null,
|
||||
) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await statisticsInputSewSupplyRateApi.getSewerageSupplyRateList(params);
|
||||
if (response.status === 'success' && response.data) {
|
||||
const commonInfo = response.data.commonInfo;
|
||||
const statusInfo = response.data.statusInfo;
|
||||
const convertedData: SewerageSupplyRateItem[] = response.data.sewerageSupplyRateList.map((d: SewerageSupplyRateItem) => ({
|
||||
const convertedData: SewerageSupplyRateItem[] = filterBySelectedAdmdstCd(
|
||||
response.data.sewerageSupplyRateList,
|
||||
sidoListRef.current,
|
||||
values,
|
||||
).map((d: SewerageSupplyRateItem) => ({
|
||||
...d,
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
spcCtpnRgnCn: d.spcCtpnRgnCn === '' || d.spcCtpnRgnCn === null ? '해당없음' : d.spcCtpnRgnCn,
|
||||
@@ -278,10 +318,15 @@ const SeweragePenetrationRateInp = () => {
|
||||
}
|
||||
: null
|
||||
);
|
||||
setFooterVerificationResult(
|
||||
remapCommonInfoVerificationToGridFields(
|
||||
commonInfo.verificationResult,
|
||||
),
|
||||
);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
if (statusInfo && statusInfo.laeSbmsnSttsCd) {
|
||||
if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo && statusInfo.lclgvSbmsnSttsCd) {
|
||||
if (['2', '3'].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
setIsEnterable(false);
|
||||
} else {
|
||||
setIsEnterable(isEnterable);
|
||||
@@ -299,6 +344,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
setRowData([]);
|
||||
setStatusInfo(null);
|
||||
setFooterFromApi(null);
|
||||
setFooterVerificationResult(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -318,7 +364,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
setPeriodCheckList(periods);
|
||||
const checkEnterable = isInputPeriodCurrent(periods);
|
||||
setLastSearchedYear(values.year || null);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values), checkEnterable);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values), checkEnterable, values);
|
||||
setLastSearchedValues(values);
|
||||
} else {
|
||||
alert(response.message);
|
||||
@@ -346,11 +392,10 @@ const SeweragePenetrationRateInp = () => {
|
||||
|
||||
const params: SubmitExceptionRequest = {
|
||||
...submitInfo,
|
||||
sewerBzmnCd: searchValues?.bsnmCd ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''),
|
||||
statsYr: searchValues?.year ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
statsCd: statsCd,
|
||||
};
|
||||
|
||||
@@ -364,7 +409,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
const response = await statisticsApi.submitException(params);
|
||||
|
||||
if (response.status === 'success') {
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false, searchValues ?? {});
|
||||
alert('제출되었습니다.');
|
||||
} else {
|
||||
alert(response.message || '데이터 제출 중 오류가 발생했습니다.');
|
||||
@@ -388,7 +433,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' }), true);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' }), true, values);
|
||||
};
|
||||
|
||||
const submitFunc = async (isException: boolean) => {
|
||||
@@ -453,52 +498,51 @@ const SeweragePenetrationRateInp = () => {
|
||||
};
|
||||
|
||||
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
|
||||
sggNm: normalizeGridString(d.sggNm),
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
trbtrNm: normalizeGridString(d.trbtrNm),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
statsTrbtrNm: nullableTrimmedString(d.statsTrbtrNm),
|
||||
spcCtpnRgnCn:
|
||||
d.spcCtpnRgnCn === '해당없음' ? null : normalizeGridString(d.spcCtpnRgnCn),
|
||||
ts1SwtrmtPpltnCnt: formatNumber(d.ts1SwtrmtPpltnCnt, 'ts1SwtrmtPpltnCnt'),
|
||||
ts2SwtrmtPpltnCnt: formatNumber(d.ts2SwtrmtPpltnCnt, 'ts2SwtrmtPpltnCnt'),
|
||||
ts3SwtrmtPpltnCnt: formatNumber(d.ts3SwtrmtPpltnCnt, 'ts3SwtrmtPpltnCnt'),
|
||||
ts1WwtPrcsPpltnCnt: formatNumber(d.ts1WwtPrcsPpltnCnt, 'ts1WwtPrcsPpltnCnt'),
|
||||
ts2WwtPrcsPpltnCnt: formatNumber(d.ts2WwtPrcsPpltnCnt, 'ts2WwtPrcsPpltnCnt'),
|
||||
ts3WwtPrcsPpltnCnt: formatNumber(d.ts3WwtPrcsPpltnCnt, 'ts3WwtPrcsPpltnCnt'),
|
||||
yetCntnPpltnCnt: formatNumber(d.yetCntnPpltnCnt, 'yetCntnPpltnCnt'),
|
||||
staaArea: formatNumber(d.staaArea, 'staaArea'),
|
||||
d.spcCtpnRgnCn === '해당없음' ? null : nullableTrimmedString(d.spcCtpnRgnCn),
|
||||
physSwtrmtPpltnCnt: formatNumber(d.physSwtrmtPpltnCnt, 'physSwtrmtPpltnCnt'),
|
||||
crtureSwtrmtPpltnCnt: formatNumber(d.crtureSwtrmtPpltnCnt, 'crtureSwtrmtPpltnCnt'),
|
||||
altdSwtrmtPpltnCnt: formatNumber(d.altdSwtrmtPpltnCnt, 'altdSwtrmtPpltnCnt'),
|
||||
physWswtPpltnCnt: formatNumber(d.physWswtPpltnCnt, 'physWswtPpltnCnt'),
|
||||
crtureWswtPpltnCnt: formatNumber(d.crtureWswtPpltnCnt, 'crtureWswtPpltnCnt'),
|
||||
altdWswtPpltnCnt: formatNumber(d.altdWswtPpltnCnt, 'altdWswtPpltnCnt'),
|
||||
urftPpltnCnt: formatNumber(d.urftPpltnCnt, 'urftPpltnCnt'),
|
||||
swtrmtZoneIntnar: formatNumber(d.swtrmtZoneIntnar, 'swtrmtZoneIntnar'),
|
||||
unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'),
|
||||
drtwPrcsPpltnCnt: formatNumber(d.drtwPrcsPpltnCnt, 'drtwPrcsPpltnCnt'),
|
||||
swgdplPpltnCnt: formatNumber(d.swgdplPpltnCnt, 'swgdplPpltnCnt'),
|
||||
spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'),
|
||||
staaExcpArea: formatNumber(d.staaExcpArea, 'staaExcpArea'),
|
||||
rmrk: normalizeGridString(d.rmrk),
|
||||
swtrmtZoneExtnar: formatNumber(d.swtrmtZoneExtnar, 'swtrmtZoneExtnar'),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'),
|
||||
wholArea: formatNumber(d.wholArea, 'wholArea'),
|
||||
whlar: formatNumber(d.whlar, 'whlar'),
|
||||
wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'),
|
||||
wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'),
|
||||
wholWwtPrcsPpltnCnt: formatNumber(d.wholWwtPrcsPpltnCnt, 'wholWwtPrcsPpltnCnt'),
|
||||
wholStaaExcpPpltnCnt: formatNumber(d.wholStaaExcpPpltnCnt, 'wholStaaExcpPpltnCnt'),
|
||||
staaPsplyRt: formatNumber(d.staaPsplyRt, 'staaPsplyRt'),
|
||||
altdPrcsPsplyRt: formatNumber(d.altdPrcsPsplyRt, 'altdPrcsPsplyRt'),
|
||||
wholWswtPpltnCnt: formatNumber(d.wholWswtPpltnCnt, 'wholWswtPpltnCnt'),
|
||||
wholStaaOtsdPpltnCnt: formatNumber(d.wholStaaOtsdPpltnCnt, 'wholStaaOtsdPpltnCnt'),
|
||||
swagePplPsplyRt: formatNumber(d.swagePplPsplyRt, 'swagePplPsplyRt'),
|
||||
awtrPsplyRt: formatNumber(d.awtrPsplyRt, 'awtrPsplyRt'),
|
||||
sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'),
|
||||
}));
|
||||
|
||||
const requestData: SewerageSupplyRateSaveRequest = {
|
||||
sewerBzmnCd: searchValues?.bsnmCd ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''),
|
||||
statsYr: searchValues?.year ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: convertedData,
|
||||
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.sewerBzmnFrgnrWholPpltnCnt,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
sewerBzmnArea: footerFromApi?.sewerBzmnArea,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
};
|
||||
|
||||
setGlobalProgressLabel('일반 검증 진행 중...');
|
||||
@@ -507,14 +551,15 @@ const SeweragePenetrationRateInp = () => {
|
||||
setGlobalProgress(50);
|
||||
|
||||
if (response.data.finalStatus === '5') {
|
||||
setGlobalProgressLabel('IMR 검증 진행 중...');
|
||||
setGlobalProgressLabel('시계열 검증 진행 중...');
|
||||
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'),
|
||||
lae_sbmsn_stts_cd: (statusInfo?.lclgvSbmsnSttsCd === '4' ? '4' : '2'),
|
||||
gen_verify_stts_cd: response.data.nvpassed ? '1' : '2',
|
||||
hb_pass_yn: response.data.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -530,13 +575,12 @@ const SeweragePenetrationRateInp = () => {
|
||||
);
|
||||
}
|
||||
setGlobalProgress(100);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {});
|
||||
await openVerificationResultDialogIfNeeded(
|
||||
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
|
||||
? {
|
||||
sewerBzmnCd: searchValues.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues.sido,
|
||||
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
|
||||
statsYr: searchValues.year,
|
||||
statsCd,
|
||||
}
|
||||
@@ -545,14 +589,13 @@ const SeweragePenetrationRateInp = () => {
|
||||
{ notify: alert },
|
||||
);
|
||||
} catch (e) {
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {});
|
||||
setIsLoading(false);
|
||||
await openVerificationResultDialogIfNeeded(
|
||||
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
|
||||
? {
|
||||
sewerBzmnCd: searchValues.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues.sido,
|
||||
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
|
||||
statsYr: searchValues.year,
|
||||
statsCd,
|
||||
}
|
||||
@@ -569,13 +612,12 @@ const SeweragePenetrationRateInp = () => {
|
||||
}
|
||||
} else {
|
||||
setGlobalProgress(100);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false, searchValues ?? {});
|
||||
await openVerificationResultDialogIfNeeded(
|
||||
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
|
||||
? {
|
||||
sewerBzmnCd: searchValues.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues.sido,
|
||||
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
|
||||
statsYr: searchValues.year,
|
||||
statsCd,
|
||||
}
|
||||
@@ -636,51 +678,50 @@ const SeweragePenetrationRateInp = () => {
|
||||
|
||||
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
|
||||
// 행정구역
|
||||
sggNm: normalizeGridString(d.sggNm), // 시군구
|
||||
admdstCd: normalizeGridString(d.admdstCd), // 행정코드
|
||||
admdstNm: normalizeGridString(d.admdstNm), // 행정구역명
|
||||
trbtrNm: normalizeGridString(d.trbtrNm), // 지류
|
||||
sggNm: nullableTrimmedString(d.sggNm), // 시군구
|
||||
admdstCd: nullableTrimmedString(d.admdstCd), // 행정코드
|
||||
admdstNm: nullableTrimmedString(d.admdstNm), // 행정구역명
|
||||
statsTrbtrNm: nullableTrimmedString(d.statsTrbtrNm), // 지류
|
||||
spcCtpnRgnCn:
|
||||
d.spcCtpnRgnCn === '해당없음'
|
||||
? null
|
||||
: normalizeGridString(d.spcCtpnRgnCn), // 특별대책지역
|
||||
: nullableTrimmedString(d.spcCtpnRgnCn), // 특별대책지역
|
||||
// 공공하수처리인구(명) - 물리적/생물학적/고도처리
|
||||
ts1SwtrmtPpltnCnt: formatNumber(d.ts1SwtrmtPpltnCnt, 'ts1SwtrmtPpltnCnt'), // 물리적처리
|
||||
ts2SwtrmtPpltnCnt: formatNumber(d.ts2SwtrmtPpltnCnt, 'ts2SwtrmtPpltnCnt'), // 생물학적처리
|
||||
ts3SwtrmtPpltnCnt: formatNumber(d.ts3SwtrmtPpltnCnt, 'ts3SwtrmtPpltnCnt'), // 고도처리
|
||||
physSwtrmtPpltnCnt: formatNumber(d.physSwtrmtPpltnCnt, 'physSwtrmtPpltnCnt'), // 물리적처리
|
||||
crtureSwtrmtPpltnCnt: formatNumber(d.crtureSwtrmtPpltnCnt, 'crtureSwtrmtPpltnCnt'), // 생물학적처리
|
||||
altdSwtrmtPpltnCnt: formatNumber(d.altdSwtrmtPpltnCnt, 'altdSwtrmtPpltnCnt'), // 고도처리
|
||||
// 폐수처리인구(명) - 물리적/생물학적/고도처리
|
||||
ts1WwtPrcsPpltnCnt: formatNumber(d.ts1WwtPrcsPpltnCnt, 'ts1WwtPrcsPpltnCnt'),
|
||||
ts2WwtPrcsPpltnCnt: formatNumber(d.ts2WwtPrcsPpltnCnt, 'ts2WwtPrcsPpltnCnt'),
|
||||
ts3WwtPrcsPpltnCnt: formatNumber(d.ts3WwtPrcsPpltnCnt, 'ts3WwtPrcsPpltnCnt'),
|
||||
yetCntnPpltnCnt: formatNumber(d.yetCntnPpltnCnt, 'yetCntnPpltnCnt'), // 미접속인구
|
||||
staaArea: formatNumber(d.staaArea, 'staaArea'), // 하수처리구역 내 면적(㎢)
|
||||
physWswtPpltnCnt: formatNumber(d.physWswtPpltnCnt, 'physWswtPpltnCnt'),
|
||||
crtureWswtPpltnCnt: formatNumber(d.crtureWswtPpltnCnt, 'crtureWswtPpltnCnt'),
|
||||
altdWswtPpltnCnt: formatNumber(d.altdWswtPpltnCnt, 'altdWswtPpltnCnt'),
|
||||
urftPpltnCnt: formatNumber(d.urftPpltnCnt, 'urftPpltnCnt'), // 미접속인구
|
||||
swtrmtZoneIntnar: formatNumber(d.swtrmtZoneIntnar, 'swtrmtZoneIntnar'), // 하수처리구역 내 면적(㎢)
|
||||
// 하수처리구역 외
|
||||
unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'), // 미처리인구
|
||||
drtwPrcsPpltnCnt: formatNumber(d.drtwPrcsPpltnCnt, 'drtwPrcsPpltnCnt'), // 오수처리인구
|
||||
swgdplPpltnCnt: formatNumber(d.swgdplPpltnCnt, 'swgdplPpltnCnt'), // 오수처리인구
|
||||
spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'), // 정화조인구
|
||||
staaExcpArea: formatNumber(d.staaExcpArea, 'staaExcpArea'), // 하수처리구역 외 면적(㎢)
|
||||
rmrk: normalizeGridString(d.rmrk), // 비고
|
||||
swtrmtZoneExtnar: formatNumber(d.swtrmtZoneExtnar, 'swtrmtZoneExtnar'), // 하수처리구역 외 면적(㎢)
|
||||
rmrk: nullableTrimmedString(d.rmrk), // 비고
|
||||
// 합계·소계(계산필드 저장용)
|
||||
wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'), // 총인구(명)
|
||||
wholArea: formatNumber(d.wholArea, 'wholArea'), // 총면적(㎢)
|
||||
whlar: formatNumber(d.whlar, 'whlar'), // 총면적(㎢)
|
||||
wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'), // 하수처리구역 내 합계
|
||||
wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'), // 공공하수처리인구 소계
|
||||
wholWwtPrcsPpltnCnt: formatNumber(d.wholWwtPrcsPpltnCnt, 'wholWwtPrcsPpltnCnt'), // 폐수처리인구 소계
|
||||
wholStaaExcpPpltnCnt: formatNumber(d.wholStaaExcpPpltnCnt, 'wholStaaExcpPpltnCnt'), // 하수처리구역 외 합계
|
||||
wholWswtPpltnCnt: formatNumber(d.wholWswtPpltnCnt, 'wholWswtPpltnCnt'), // 폐수처리인구 소계
|
||||
wholStaaOtsdPpltnCnt: formatNumber(d.wholStaaOtsdPpltnCnt, 'wholStaaOtsdPpltnCnt'), // 하수처리구역 외 합계
|
||||
// 보급률(%)
|
||||
staaPsplyRt: formatNumber(d.staaPsplyRt, 'staaPsplyRt'), // 공공하수처리구역 인구보급률
|
||||
altdPrcsPsplyRt: formatNumber(d.altdPrcsPsplyRt, 'altdPrcsPsplyRt'), // 고도처리 인구보급률
|
||||
swagePplPsplyRt: formatNumber(d.swagePplPsplyRt, 'swagePplPsplyRt'), // 공공하수처리구역 인구보급률
|
||||
awtrPsplyRt: formatNumber(d.awtrPsplyRt, 'awtrPsplyRt'), // 고도처리 인구보급률
|
||||
sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'), // 하수도 설치율
|
||||
}));
|
||||
|
||||
const requestData: SewerageSupplyRateSaveRequest = {
|
||||
sewerBzmnCd: searchValues?.bsnmCd ?? '', // 하수도 사업자 코드
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? '', // 시도
|
||||
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''),
|
||||
statsYr: searchValues?.year ?? '', // 통계 연도
|
||||
userId: userInfo.userUnqId, // 사용자
|
||||
userUuid: userInfo.userUnqId, // 사용자
|
||||
saveList: convertedData, // 저장 데이터
|
||||
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.sewerBzmnFrgnrWholPpltnCnt // 행정참고자료 외국인 합계
|
||||
sewerBzmnArea: footerFromApi?.sewerBzmnArea, // 행정참고자료면적
|
||||
};
|
||||
|
||||
if (isAdminInfo) {
|
||||
@@ -692,7 +733,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
if (response.status === 'success') {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgress(100);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {});
|
||||
alert('저장되었습니다.');
|
||||
} else {
|
||||
alert(response.message || '데이터 저장 중 오류가 발생했습니다.');
|
||||
@@ -719,18 +760,17 @@ const SeweragePenetrationRateInp = () => {
|
||||
const userInfo = await getUserInfo();
|
||||
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: searchValues?.bsnmCd ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ""),
|
||||
statsYr: searchValues?.year ?? '',
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCd,
|
||||
};
|
||||
|
||||
const response = await statisticsApi.cancelSubmit(requestData);
|
||||
|
||||
if (response.status === 'success') {
|
||||
fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {});
|
||||
alert('제출 취소되었습니다.');
|
||||
} else {
|
||||
alert(response.message ?? '제출 취소 중 오류가 발생했습니다.');
|
||||
@@ -749,13 +789,14 @@ const SeweragePenetrationRateInp = () => {
|
||||
setVerificationHistoryOpen(true);
|
||||
};
|
||||
|
||||
const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || '')
|
||||
? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL')
|
||||
: true;
|
||||
const toolbarSearchContext = lastSearchedValues;
|
||||
const gugunOkOrNotDoList = isGugunOkForToolbar(sidoList, toolbarSearchContext);
|
||||
const submitOk = isSubmitOkForToolbar(sidoList, toolbarSearchContext);
|
||||
|
||||
const inputPeriodCurrent = isInputPeriodCurrent(periodCheckList);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === '2';
|
||||
const baseShowSaveInToolbar = (isEnterable || isAdminInfo) && gugunOkOrNotDoList;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === '2';
|
||||
const baseShowSaveInToolbar =
|
||||
(isEnterable || isAdminInfo) && inputPeriodCurrent && gugunOkOrNotDoList;
|
||||
const baseShowEnterableToolbarButtons = isEnterable && gugunOkOrNotDoList;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && inputPeriodCurrent && gugunOkOrNotDoList;
|
||||
@@ -773,6 +814,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
@@ -789,10 +831,10 @@ const SeweragePenetrationRateInp = () => {
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
<LastYearDataButton getLastYearDataFunc={() => getLastYearDataFunc(searchValues ?? {})} />
|
||||
<SubmitButton submitFunc={() => submitFunc(false)} />
|
||||
{submitOk && <SubmitButton submitFunc={() => submitFunc(false)} />}
|
||||
</>
|
||||
)}
|
||||
{statusInfo?.laeSbmsnSttsCd === '2' && <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />}
|
||||
{statusInfo?.lclgvSbmsnSttsCd === '2' && submitOk && <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
@@ -805,9 +847,9 @@ const SeweragePenetrationRateInp = () => {
|
||||
</div>
|
||||
}
|
||||
<DataGrid
|
||||
key={(isEnterable || isAdminInfo) ? 'enterable' : 'readonly'}
|
||||
key={((isEnterable || isAdminInfo) && inputPeriodCurrent) ? 'enterable' : 'readonly'}
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={(rowData.length > 0) && isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true)}
|
||||
isExcelImport={(rowData.length > 0) && isEnterable && gugunOkOrNotDoList}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
rowStartNum={3}
|
||||
@@ -826,6 +868,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
return hasImr ? 'rg-cell-verify-error-imr' : 'rg-cell-verify-error';
|
||||
}
|
||||
}}
|
||||
footerVerificationResult={footerVerificationResult}
|
||||
cellTooltipCallback={({ row, fieldName }) => {
|
||||
const verification = (row as GridRowWithVerification).verificationResult;
|
||||
if (!verification) return;
|
||||
@@ -833,9 +876,9 @@ const SeweragePenetrationRateInp = () => {
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
return errors
|
||||
.map((e) => {
|
||||
const prefix = e.vrfcCd === 'IMR' ? 'IMR검증 - ' : (e.vrfcCd === 'NV' ? '일반검증 - ' : 'HB검증 -');
|
||||
const prefix = e.vrfcCd === 'IMR' ? '시계열 검증 - ' : (e.vrfcCd === 'NV' ? '일반검증 - ' : '모집단 검증 -');
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
@@ -862,9 +905,8 @@ const SeweragePenetrationRateInp = () => {
|
||||
queryContext={
|
||||
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
|
||||
? {
|
||||
sewerBzmnCd: searchValues.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues.sido,
|
||||
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
|
||||
statsYr: searchValues.year,
|
||||
statsCd,
|
||||
}
|
||||
@@ -882,12 +924,8 @@ const SeweragePenetrationRateInp = () => {
|
||||
queryContext={
|
||||
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
|
||||
? {
|
||||
sewerBzmnCd: searchValues.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues.sido,
|
||||
sggNm:
|
||||
searchValues.gugun === "ALL"
|
||||
? ""
|
||||
: (searchValues.gugun ?? ""),
|
||||
statsYr: searchValues.year,
|
||||
statsCd,
|
||||
}
|
||||
|
||||
@@ -2,33 +2,13 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { ComplaintInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ComplaintInpColumn";
|
||||
import { ResidentFriendlyFactInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ResidentFriendlyFactInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { statisticsInputSewerageServiceLevelApi } from "@/entities/statistics/api/input/statistics-input-sewerage-service-level-service";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsInputSewerageServiceLevelApi } from "@/entities/statistics/api/input/statistics-input-sewerage-service-level-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import {
|
||||
SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD,
|
||||
SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD,
|
||||
@@ -39,20 +19,55 @@ import {
|
||||
type SewerageServiceLevelResidentFriendlyFactSaveItem,
|
||||
type SewerageServiceLevelVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewerage-service-level-interface";
|
||||
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type {
|
||||
SewerageStatusInfo,
|
||||
cancelSubmitRequest,
|
||||
ImrStatusResponse,
|
||||
SewerageStatusInfo,
|
||||
Sido,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import {
|
||||
filterBySelectedAdmdstCd,
|
||||
isGugunOkForToolbar,
|
||||
isSubmitOkForToolbar,
|
||||
resolveNoApplicableForRequest,
|
||||
toInputSearchRegion,
|
||||
} from "@/entities/statistics";
|
||||
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { ComplaintInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ComplaintInpColumn";
|
||||
import { ResidentFriendlyFactInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ResidentFriendlyFactInpColumn";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
const SewerageServiceLevelInpTabList = [
|
||||
ComplaintInpColumn,
|
||||
ResidentFriendlyFactInpColumn,
|
||||
@@ -60,11 +75,6 @@ const SewerageServiceLevelInpTabList = [
|
||||
|
||||
const firstTabKey = ComplaintInpColumn.tabKey;
|
||||
|
||||
const SEWERAGE_SERVICE_LEVEL_COLUMN_GUIDE_STATS_CDS = [
|
||||
SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD,
|
||||
SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD,
|
||||
] as const;
|
||||
|
||||
const SEWERAGE_SERVICE_LEVEL_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY: Record<string, string> = {
|
||||
[ComplaintInpColumn.tabKey]: SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD,
|
||||
[ResidentFriendlyFactInpColumn.tabKey]: SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD,
|
||||
@@ -109,8 +119,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -118,10 +128,22 @@ const computeTabEnterable = (
|
||||
return periodEnterable;
|
||||
};
|
||||
|
||||
const sidoListRef: { current: Sido[] } = { current: [] };
|
||||
|
||||
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return doList.includes(values?.bsnmCd || "")
|
||||
? Boolean(values?.gugun && values.gugun !== "ALL")
|
||||
: true;
|
||||
return isGugunOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
||||
return isSubmitOkForToolbar(sidoListRef.current, values);
|
||||
}
|
||||
|
||||
function buildNoApplicableForRequest(
|
||||
values: SearchFormValues | null,
|
||||
checked: boolean,
|
||||
isSaveListEmpty: boolean,
|
||||
): boolean {
|
||||
return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty);
|
||||
}
|
||||
|
||||
function computeTabToolbarSnapshot(
|
||||
@@ -134,8 +156,8 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -153,17 +175,16 @@ function buildSewerageServiceLevelSearchParam(
|
||||
): SewerageServiceLevelComplaintRequestParams {
|
||||
return {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function mapServiceLevelListToGridRows<
|
||||
T extends { verificationResult?: SewerageServiceLevelVerificationResult },
|
||||
>(list: T[], ctpvNm: string): GridRowWithVerification[] {
|
||||
return list.map((item) => {
|
||||
>(list: T[], ctpvNm: string, values?: SearchFormValues | null): GridRowWithVerification[] {
|
||||
return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
|
||||
const { verificationResult, ...rest } = item;
|
||||
const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification;
|
||||
if (verificationResult) row.verificationResult = verificationResult;
|
||||
@@ -171,17 +192,6 @@ function mapServiceLevelListToGridRows<
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeGridString(value: unknown): string {
|
||||
if (value === null || value === undefined) return "";
|
||||
return typeof value === "string" ? value : String(value);
|
||||
}
|
||||
|
||||
function normalizeGridRmrk(value: unknown): string | null {
|
||||
if (value === null || value === undefined) return null;
|
||||
const s = typeof value === "string" ? value : String(value);
|
||||
return s.trim() === "" ? null : s;
|
||||
}
|
||||
|
||||
function buildColumnFormatNumber(
|
||||
columns: { fieldName?: string; dataFormat?: string }[],
|
||||
) {
|
||||
@@ -210,21 +220,21 @@ function buildComplaintSaveList(
|
||||
const formatNumber = buildColumnFormatNumber(ComplaintInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
swtrmtPpltnCnt: formatNumber(d.swtrmtPpltnCnt, "swtrmtPpltnCnt"),
|
||||
dschgWqCnt: formatNumber(d.dschgWqCnt, "dschgWqCnt"),
|
||||
odorCnt: formatNumber(d.odorCnt, "odorCnt"),
|
||||
ldsCnt: formatNumber(d.ldsCnt, "ldsCnt"),
|
||||
plltnAcdntCnt: formatNumber(d.plltnAcdntCnt, "plltnAcdntCnt"),
|
||||
swagePplCnt: formatNumber(d.swagePplCnt, "swagePplCnt"),
|
||||
sewerCrgAmt: formatNumber(d.sewerCrgAmt, "sewerCrgAmt"),
|
||||
etcCnt: formatNumber(d.etcCnt, "etcCnt"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
whol: formatNumber(d.whol, "whol"),
|
||||
crtrPpltnCvlcptEvntcsCnt: formatNumber(
|
||||
d.crtrPpltnCvlcptEvntcsCnt,
|
||||
"crtrPpltnCvlcptEvntcsCnt",
|
||||
cvlcptDschgWq: formatNumber(d.cvlcptDschgWq, "cvlcptDschgWq"),
|
||||
cvlcptOdorCnt: formatNumber(d.cvlcptOdorCnt, "cvlcptOdorCnt"),
|
||||
cvlcptDrgCnt: formatNumber(d.cvlcptDrgCnt, "cvlcptDrgCnt"),
|
||||
cvlcptMnhlCnt: formatNumber(d.cvlcptMnhlCnt, "cvlcptMnhlCnt"),
|
||||
cvlcptSpctnkCnt: formatNumber(d.cvlcptSpctnkCnt, "cvlcptSpctnkCnt"),
|
||||
cvlcptSewerCrg: formatNumber(d.cvlcptSewerCrg, "cvlcptSewerCrg"),
|
||||
cvlcptEtcCnt: formatNumber(d.cvlcptEtcCnt, "cvlcptEtcCnt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
cvlcptWholCnt: formatNumber(d.cvlcptWholCnt, "cvlcptWholCnt"),
|
||||
crtrPpltnCvlcptCnt: formatNumber(
|
||||
d.crtrPpltnCvlcptCnt,
|
||||
"crtrPpltnCvlcptCnt",
|
||||
),
|
||||
}));
|
||||
}
|
||||
@@ -235,21 +245,21 @@ function buildResidentFriendlyFactSaveList(
|
||||
const formatNumber = buildColumnFormatNumber(ResidentFriendlyFactInpColumn.columns);
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: normalizeGridString(d.admdstCd),
|
||||
admdstNm: normalizeGridString(d.admdstNm),
|
||||
swtrmtFcltCd: normalizeGridString(d.swtrmtFcltCd),
|
||||
swtrmtFcltNm: normalizeGridString(d.swtrmtFcltNm),
|
||||
fcltSn: normalizeGridString(d.fcltSn),
|
||||
tmplntArea: formatNumber(d.tmplntArea, "tmplntArea"),
|
||||
brkFcltArea: formatNumber(d.brkFcltArea, "brkFcltArea"),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(d.swtrmtFcltNm),
|
||||
fcltSn: nullableTrimmedString(d.fcltSn),
|
||||
stpar: formatNumber(d.stpar, "stpar"),
|
||||
brkFcar: formatNumber(d.brkFcar, "brkFcar"),
|
||||
childFrshpSpacArea: formatNumber(d.childFrshpSpacArea, "childFrshpSpacArea"),
|
||||
bdySportsFcltArea: formatNumber(d.bdySportsFcltArea, "bdySportsFcltArea"),
|
||||
sportsFcltArea: formatNumber(d.sportsFcltArea, "sportsFcltArea"),
|
||||
pkltArea: formatNumber(d.pkltArea, "pkltArea"),
|
||||
eduMultiFcltArea: formatNumber(d.eduMultiFcltArea, "eduMultiFcltArea"),
|
||||
bdySportsFcar: formatNumber(d.bdySportsFcar, "bdySportsFcar"),
|
||||
sportsFcar: formatNumber(d.sportsFcar, "sportsFcar"),
|
||||
pkltar: formatNumber(d.pkltar, "pkltar"),
|
||||
eduMultiFcar: formatNumber(d.eduMultiFcar, "eduMultiFcar"),
|
||||
wtlndMakeArea: formatNumber(d.wtlndMakeArea, "wtlndMakeArea"),
|
||||
etcCnt: formatNumber(d.etcCnt, "etcCnt"),
|
||||
rmrk: normalizeGridRmrk(d.rmrk),
|
||||
othrar: formatNumber(d.othrar, "othrar"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -271,15 +281,17 @@ async function waitForImrJobCompleted(
|
||||
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} 상태로 종료되었습니다.`);
|
||||
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
}
|
||||
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
|
||||
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
||||
}
|
||||
|
||||
// 하수도 통계 관리 > 13. 하수도 서비스 수준
|
||||
const SewerageServiceLevelInp = () => {
|
||||
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
||||
sidoListRef.current = sidoList;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
@@ -307,9 +319,15 @@ const SewerageServiceLevelInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [columnGuidesByTab, setColumnGuidesByTab] = useState<
|
||||
Record<string, Record<string, string>>
|
||||
>({});
|
||||
@@ -326,42 +344,30 @@ const SewerageServiceLevelInp = () => {
|
||||
const publicSewageFacilitiesAvailableRef = useRef(false);
|
||||
activeTabRef.current = activeTab;
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_SERVICE_LEVEL) return;
|
||||
const tabKey = activeTab;
|
||||
if (!tabKey) return;
|
||||
if (Object.prototype.hasOwnProperty.call(columnGuidesByTab, tabKey)) return;
|
||||
const statsCd =
|
||||
SEWERAGE_SERVICE_LEVEL_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY[tabKey] ?? tabKey;
|
||||
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
SEWERAGE_SERVICE_LEVEL_COLUMN_GUIDE_STATS_CDS.map(async (statsCd) => {
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(statsCd);
|
||||
if (res.status !== "success" || !Array.isArray(res.data)) {
|
||||
return [statsCd, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [statsCd, next] as const;
|
||||
} catch {
|
||||
return [statsCd, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
const next = await fetchColumnGuidesRecord(statsCd);
|
||||
if (cancelled) return;
|
||||
const guidesByStatsCd = Object.fromEntries(entries);
|
||||
setColumnGuidesByTab(
|
||||
Object.fromEntries(
|
||||
Object.entries(SEWERAGE_SERVICE_LEVEL_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY).map(
|
||||
([tabKey, statsCd]) => [tabKey, guidesByStatsCd[statsCd] ?? {}],
|
||||
),
|
||||
),
|
||||
setColumnGuidesByTab((prev) =>
|
||||
Object.prototype.hasOwnProperty.call(prev, tabKey)
|
||||
? prev
|
||||
: { ...prev, [tabKey]: next },
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [activeTabId, activeTab, columnGuidesByTab]);
|
||||
|
||||
const commitToolbarSnapshotForLoadedTab = (
|
||||
tabKey: string,
|
||||
@@ -384,6 +390,13 @@ const SewerageServiceLevelInp = () => {
|
||||
);
|
||||
return next;
|
||||
});
|
||||
if (searchContext) {
|
||||
setSearchContextByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, searchContext);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const applyRowMap = (
|
||||
@@ -425,6 +438,7 @@ const SewerageServiceLevelInp = () => {
|
||||
const gridRows = mapServiceLevelListToGridRows(
|
||||
(complaintList ?? []) as SewerageServiceLevelComplaintItem[],
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
@@ -435,6 +449,9 @@ const SewerageServiceLevelInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
setSearchLoaded(true);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
@@ -454,6 +471,7 @@ const SewerageServiceLevelInp = () => {
|
||||
const gridRows = mapServiceLevelListToGridRows(
|
||||
(residentFriendlyFactList ?? []) as SewerageServiceLevelResidentFriendlyFactItem[],
|
||||
commonInfo.ctpvNm,
|
||||
searchContext,
|
||||
);
|
||||
if (!fromPreYear) {
|
||||
publicSewageFacilitiesAvailableRef.current = gridRows.length > 0;
|
||||
@@ -470,6 +488,9 @@ const SewerageServiceLevelInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
setSearchLoaded(true);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
@@ -493,7 +514,9 @@ const SewerageServiceLevelInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
setLastSearchedValues(null);
|
||||
@@ -538,7 +561,9 @@ const SewerageServiceLevelInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setSearchContextByTab(new Map());
|
||||
setIsNotApplicable(false);
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -657,9 +682,19 @@ const SewerageServiceLevelInp = () => {
|
||||
const tabKey = activeTabRef.current;
|
||||
const isResidentTab = tabKey === ResidentFriendlyFactInpColumn.tabKey;
|
||||
const latestRows = getTabGridRows(tabKey);
|
||||
const noApplicable = isResidentTab && isNotApplicable;
|
||||
const noApplicable =
|
||||
isResidentTab &&
|
||||
buildNoApplicableForRequest(
|
||||
searchContextByTab.get(tabKey) ?? lastSearchedValues,
|
||||
isNotApplicable,
|
||||
latestRows.length === 0,
|
||||
);
|
||||
if (latestRows.length === 0) {
|
||||
if (isResidentTab && !noApplicable) {
|
||||
if (
|
||||
isResidentTab &&
|
||||
!noApplicable &&
|
||||
buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)
|
||||
) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -700,7 +735,13 @@ const SewerageServiceLevelInp = () => {
|
||||
return next;
|
||||
});
|
||||
|
||||
const noApplicable = isResidentTab && isNotApplicable;
|
||||
const noApplicable =
|
||||
isResidentTab &&
|
||||
buildNoApplicableForRequest(
|
||||
searchContextByTab.get(tabKey) ?? lastSearchedValues,
|
||||
isNotApplicable,
|
||||
latestRows.length === 0,
|
||||
);
|
||||
const complaintSubmitList = buildComplaintSaveList(latestRows);
|
||||
const residentSubmitList = noApplicable
|
||||
? []
|
||||
@@ -710,7 +751,12 @@ const SewerageServiceLevelInp = () => {
|
||||
alert("제출할 데이터가 없습니다.");
|
||||
return;
|
||||
}
|
||||
if (isResidentTab && !noApplicable && residentSubmitList.length === 0) {
|
||||
if (
|
||||
isResidentTab &&
|
||||
!noApplicable &&
|
||||
residentSubmitList.length === 0 &&
|
||||
buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)
|
||||
) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -725,28 +771,20 @@ const SewerageServiceLevelInp = () => {
|
||||
const submitInfoBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
};
|
||||
|
||||
const verificationQuery = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
@@ -769,13 +807,13 @@ const SewerageServiceLevelInp = () => {
|
||||
const response = isComplaintTab
|
||||
? await statisticsInputSewerageServiceLevelApi.submitComplaintList({
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
submitList: complaintSubmitList,
|
||||
})
|
||||
: await statisticsInputSewerageServiceLevelApi.submitResidentFriendlyFactList({
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
noApplicable,
|
||||
submitList: residentSubmitList,
|
||||
@@ -791,14 +829,15 @@ const SewerageServiceLevelInp = () => {
|
||||
|
||||
if (finalStatus === "5") {
|
||||
setGlobalProgress(50);
|
||||
setGlobalProgressLabel("IMR 검증 진행 중...");
|
||||
setGlobalProgressLabel("시계열 검증 진행 중...");
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "",
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -875,15 +914,17 @@ const SewerageServiceLevelInp = () => {
|
||||
return next;
|
||||
});
|
||||
|
||||
const noApplicable = isResidentTab && isNotApplicable;
|
||||
const noApplicable =
|
||||
isResidentTab &&
|
||||
buildNoApplicableForRequest(
|
||||
searchContextByTab.get(tabKey) ?? lastSearchedValues,
|
||||
isNotApplicable,
|
||||
latestRows.length === 0,
|
||||
);
|
||||
const baseRequest = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -900,7 +941,7 @@ const SewerageServiceLevelInp = () => {
|
||||
const response =
|
||||
await statisticsInputSewerageServiceLevelApi.saveComplaintList({
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
saveList,
|
||||
});
|
||||
@@ -913,7 +954,11 @@ const SewerageServiceLevelInp = () => {
|
||||
const saveList = noApplicable
|
||||
? []
|
||||
: buildResidentFriendlyFactSaveList(latestRows);
|
||||
if (!noApplicable && saveList.length === 0) {
|
||||
if (
|
||||
!noApplicable &&
|
||||
saveList.length === 0 &&
|
||||
buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)
|
||||
) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -922,7 +967,7 @@ const SewerageServiceLevelInp = () => {
|
||||
await statisticsInputSewerageServiceLevelApi.saveResidentFriendlyFactList(
|
||||
{
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
noApplicable,
|
||||
saveList,
|
||||
@@ -973,14 +1018,10 @@ const SewerageServiceLevelInp = () => {
|
||||
}
|
||||
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -1016,10 +1057,10 @@ const SewerageServiceLevelInp = () => {
|
||||
for (const tab of SewerageServiceLevelInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1036,15 +1077,15 @@ const SewerageServiceLevelInp = () => {
|
||||
return map;
|
||||
}, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]);
|
||||
|
||||
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
||||
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
||||
const activeTabRowData = rowDataMap.get(activeTab) ?? [];
|
||||
const activeTabToolbar =
|
||||
toolbarSnapshotByTab.get(activeTab) ??
|
||||
computeTabToolbarSnapshot(
|
||||
activeTabStatus,
|
||||
tabRowDataExists(rowDataMap, activeTab),
|
||||
hasSearched,
|
||||
lastSearchedValues,
|
||||
searchContextForToolbar,
|
||||
inputPeriodEnterable,
|
||||
isAdminInfo,
|
||||
);
|
||||
@@ -1055,12 +1096,14 @@ const SewerageServiceLevelInp = () => {
|
||||
activeTabToolbar.showEnterableToolbarButtons && toolbarGroupAllowed;
|
||||
const showActionToolbarCard =
|
||||
activeTabToolbar.showActionToolbarCard && toolbarGroupAllowed;
|
||||
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
||||
const showNotApplicableForResidentTab =
|
||||
activeTab === ResidentFriendlyFactInpColumn.tabKey &&
|
||||
searchLoaded &&
|
||||
hasSearched &&
|
||||
activeTabRowData.length === 0 &&
|
||||
computeTabEnterable(activeTabStatus, inputPeriodEnterable);
|
||||
computeTabEnterable(activeTabStatus, inputPeriodEnterable) &&
|
||||
submitOk;
|
||||
const showSecondaryToolbarCard =
|
||||
hasSearched &&
|
||||
toolbarGroupAllowed &&
|
||||
@@ -1071,9 +1114,8 @@ const SewerageServiceLevelInp = () => {
|
||||
const statsCd = tabStatsCdMap.get(activeTab);
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
||||
return {
|
||||
sewerBzmnCd: ctx.bsnmCd,
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido,
|
||||
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
||||
statsYr: ctx.year,
|
||||
statsCd,
|
||||
};
|
||||
@@ -1110,11 +1152,11 @@ const SewerageServiceLevelInp = () => {
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR"
|
||||
? "IMR검증 - "
|
||||
? "시계열 검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "HB검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1136,6 +1178,7 @@ const SewerageServiceLevelInp = () => {
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
isInput
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
@@ -1143,8 +1186,8 @@ const SewerageServiceLevelInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
@@ -1156,19 +1199,19 @@ const SewerageServiceLevelInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void getLastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showSecondaryToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showNotApplicableForResidentTab && (
|
||||
<NotApplicableCheckButton
|
||||
isCheck={isNotApplicable}
|
||||
@@ -1203,11 +1246,11 @@ const SewerageServiceLevelInp = () => {
|
||||
const tabKey = info.tabKey;
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
|
||||
return (
|
||||
@@ -1245,6 +1288,9 @@ const SewerageServiceLevelInp = () => {
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { ColumnConfig } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
const StatisticsReviewHistory = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
@@ -69,7 +70,7 @@ const StatisticsReviewHistory = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
||||
<SearchForm isInput onSubmit={handleSearch} onReset={handleReset} />
|
||||
{isLoading && (
|
||||
<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]">
|
||||
|
||||
@@ -4,32 +4,33 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { RealGridReact } from "realgrid-react";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
|
||||
import { useActiveTabRegAuthorityAllowed, useAlertStore } from "@/shared/lib";
|
||||
import { useActiveTabRegAuthorityAllowed, useAlertStore, useSearchConditionSidoList } from "@/shared/lib";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { StatisticsInputStatusApi } from "@/entities/statistics/api/input/statistics-input-status-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
isInputPeriodCurrent,
|
||||
type CheckInputPeriodItem,
|
||||
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { StatsInputStatusApproveItem } from "@/entities/statistics/model/statistics-interface";
|
||||
import type {
|
||||
StatsInputStatusListApiResponse,
|
||||
StatsInputStatusListRequestParams,
|
||||
} from "@/entities/statistics/model/input/statistics-input-status-interface";
|
||||
import {
|
||||
type CheckInputPeriodItem,
|
||||
isInputPeriodCurrent,
|
||||
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import type { StatsInputStatusApproveItem } from "@/entities/statistics/model/statistics-interface";
|
||||
import { isGugunOkForToolbar } from "@/entities/statistics";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
ExceptionProcessingHistoryDialog,
|
||||
type ExceptionProcessingHistoryQueryContext,
|
||||
} from "@/components/module/ExceptionProcessingHistoryDialog";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { StatsInputStatusInpColumn } from "@/components/util/column/input/StatsInputStatusInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -43,6 +44,7 @@ const getStatsInputStatusFetcher = async (
|
||||
// 하수도 통계 관리 > 00. 통계 입력 현황
|
||||
const StatsInputStatusInp = () => {
|
||||
|
||||
const { sidoList } = useSearchConditionSidoList();
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const activeTabId = useTabStore((state) => state.activeTabId);
|
||||
@@ -174,8 +176,8 @@ const StatsInputStatusInp = () => {
|
||||
setIsApproveLoading(true);
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const mdfrId = (userInfo.userUnqId ?? "").trim();
|
||||
if (!mdfrId) {
|
||||
const mdfrUuid = (userInfo.userUnqId ?? "").trim();
|
||||
if (!mdfrUuid) {
|
||||
alert("수정자 ID를 확인할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
@@ -187,7 +189,7 @@ const StatsInputStatusInp = () => {
|
||||
.map((row) => {
|
||||
const statsCd = String(row.statsCd ?? "").trim();
|
||||
const sewerBzmnCd = String(row.sewerBzmnCd ?? sewerBzmnCdDefault).trim();
|
||||
return { statsYr, sewerBzmnCd, statsCd, mdfrId };
|
||||
return { statsYr, sewerBzmnCd, statsCd, mdfrUuid };
|
||||
})
|
||||
.filter((item) => item.statsCd.length > 0);
|
||||
|
||||
@@ -253,9 +255,7 @@ const StatsInputStatusInp = () => {
|
||||
await mutate(["stats-input-status", searchParams]);
|
||||
}, [mutate, searchParams]);
|
||||
|
||||
const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || "")
|
||||
? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== "ALL")
|
||||
: true;
|
||||
const gugunOkOrNotDoList = isGugunOkForToolbar(sidoList, lastSearchedValues);
|
||||
|
||||
const allRowsLaeSbmsnApproved = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { statisticsInputSubmitInfoSchema, type StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type { SewerageStatusInfo } from "@/entities/statistics/model/statistics-interface";
|
||||
import { Controller,useForm } from "react-hook-form";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
import { type StatisticsInputSubmitInfoFormData,statisticsInputSubmitInfoSchema } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import type { SewerageStatusInfo } from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -14,12 +15,12 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Input,
|
||||
LoadingWithAnimation,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
LoadingWithAnimation,
|
||||
} from "@common/components";
|
||||
|
||||
/** 숫자만 추출 후 하이픈 포맷 (010-1234-5678, 02-123-4567 등) */
|
||||
@@ -39,7 +40,7 @@ function formatPhoneWithHyphen(value: string): string {
|
||||
return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6, 10)}`;
|
||||
}
|
||||
|
||||
const PHONE_FIELDS: (keyof StatisticsInputSubmitInfoFormData)[] = ["laePicTelNo", "laeIdfrTelNo"];
|
||||
const PHONE_FIELDS: (keyof StatisticsInputSubmitInfoFormData)[] = ["lclgvPicTelnoCn", "lclgvIdfrTelnoCn"];
|
||||
|
||||
interface SubmitDialogProps {
|
||||
open: boolean;
|
||||
@@ -55,26 +56,26 @@ interface SubmitDialogProps {
|
||||
}
|
||||
|
||||
const defaultValues: StatisticsInputSubmitInfoFormData = {
|
||||
laePicNm: "",
|
||||
laeKbrdrJbgdNm: "",
|
||||
laeKbrdrDeptNm: "",
|
||||
laePicTelNo: "",
|
||||
laeIdfrNm: "",
|
||||
laeIdfrJbgdNm: "",
|
||||
laeIdfrDeptNm: "",
|
||||
laeIdfrTelNo: "",
|
||||
lclgvPicNm: "",
|
||||
lclgvKbrdrJbgdNm: "",
|
||||
lclgvKbrdrDeptNm: "",
|
||||
lclgvPicTelnoCn: "",
|
||||
lclgvIdfrNm: "",
|
||||
lclgvIdfrJbgdNm: "",
|
||||
lclgvIdfrDeptNm: "",
|
||||
lclgvIdfrTelnoCn: "",
|
||||
};
|
||||
|
||||
function statusInfoToFormData(info: SewerageStatusInfo): StatisticsInputSubmitInfoFormData {
|
||||
return {
|
||||
laePicNm: info.laePicNm ?? "",
|
||||
laeKbrdrJbgdNm: info.laeKbrdrJbgdNm ?? "",
|
||||
laeKbrdrDeptNm: info.laeKbrdrDeptNm ?? "",
|
||||
laePicTelNo: info.laePicTelNo ?? "",
|
||||
laeIdfrNm: info.laeIdfrNm ?? "",
|
||||
laeIdfrJbgdNm: info.laeIdfrJbgdNm ?? "",
|
||||
laeIdfrDeptNm: info.laeIdfrDeptNm ?? "",
|
||||
laeIdfrTelNo: info.laeIdfrTelNo ?? "",
|
||||
lclgvPicNm: info.lclgvPicNm ?? "",
|
||||
lclgvKbrdrJbgdNm: info.lclgvKbrdrJbgdNm ?? "",
|
||||
lclgvKbrdrDeptNm: info.lclgvKbrdrDeptNm ?? "",
|
||||
lclgvPicTelnoCn: info.lclgvPicTelnoCn ?? "",
|
||||
lclgvIdfrNm: info.lclgvIdfrNm ?? "",
|
||||
lclgvIdfrJbgdNm: info.lclgvIdfrJbgdNm ?? "",
|
||||
lclgvIdfrDeptNm: info.lclgvIdfrDeptNm ?? "",
|
||||
lclgvIdfrTelnoCn: info.lclgvIdfrTelnoCn ?? "",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -114,14 +115,14 @@ const SubmitDailog = ({
|
||||
}
|
||||
|
||||
const rows: { name: keyof StatisticsInputSubmitInfoFormData; label: string; placeholder: string }[] = [
|
||||
{ name: "laePicNm", label: "작성자 명", placeholder: "작성자 명 입력" },
|
||||
{ name: "laeKbrdrJbgdNm", label: "작성자 직제", placeholder: "작성자 직제 입력" },
|
||||
{ name: "laeKbrdrDeptNm", label: "작성자 부서", placeholder: "작성자 부서 입력" },
|
||||
{ name: "laePicTelNo", label: "작성자 연락처", placeholder: "작성자 연락처 입력" },
|
||||
{ name: "laeIdfrNm", label: "승인자명", placeholder: "승인자명 입력" },
|
||||
{ name: "laeIdfrJbgdNm", label: "승인자 직제", placeholder: "승인자 직제 입력" },
|
||||
{ name: "laeIdfrDeptNm", label: "승인자 부서", placeholder: "승인자 부서 입력" },
|
||||
{ name: "laeIdfrTelNo", label: "승인자 연락처", placeholder: "승인자 연락처" },
|
||||
{ name: "lclgvPicNm", label: "작성자 명", placeholder: "작성자 명 입력" },
|
||||
{ name: "lclgvKbrdrJbgdNm", label: "작성자 직제", placeholder: "작성자 직제 입력" },
|
||||
{ name: "lclgvKbrdrDeptNm", label: "작성자 부서", placeholder: "작성자 부서 입력" },
|
||||
{ name: "lclgvPicTelnoCn", label: "작성자 연락처", placeholder: "작성자 연락처 입력" },
|
||||
{ name: "lclgvIdfrNm", label: "승인자명", placeholder: "승인자명 입력" },
|
||||
{ name: "lclgvIdfrJbgdNm", label: "승인자 직제", placeholder: "승인자 직제 입력" },
|
||||
{ name: "lclgvIdfrDeptNm", label: "승인자 부서", placeholder: "승인자 부서 입력" },
|
||||
{ name: "lclgvIdfrTelnoCn", label: "승인자 연락처", placeholder: "승인자 연락처" },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
import { Button } from "@common/components";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
AdmdstRenewalReducedColumnConfig,
|
||||
AdmdstRenewalAddedColumnConfig,
|
||||
} from "@/components/util/column/management/AdmdstRenewalColumn";
|
||||
|
||||
import { UpperCodeSelectDialog } from "@/components/statistics/management/UpperCodeSelectDialog";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { useAdmdstRenewalTab } from "@/components/statistics/management/hooks/useAdmdstRenewalTab";
|
||||
import { asGridRowData, asGridRowSetter, fromGridRow } from "@/components/statistics/management/lib/gridCast";
|
||||
import type { AdmdstRenewalAddedItem } from "@/components/statistics/management/types/population-tab-types";
|
||||
import { UpperCodeSelectDialog } from "@/components/statistics/management/UpperCodeSelectDialog";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
AdmdstRenewalAddedColumnConfig,
|
||||
AdmdstRenewalReducedColumnConfig,
|
||||
} from "@/components/util/column/management/AdmdstRenewalColumn";
|
||||
|
||||
const reducedColumnInfo: ColumnInfo = {
|
||||
columns: AdmdstRenewalReducedColumnConfig.columns ?? [],
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { Button } from "@common/components";
|
||||
import { useRef } from "react";
|
||||
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import type { DomesticPopulationItem } from "@/entities/statistics/model/ppltn/statistics-domestic-population-interface";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { usePopulationMappingTab } from "@/components/statistics/management/hooks/usePopulationMappingTab";
|
||||
import { asGridRowData, asGridRowSetter, fromGridRow } from "@/components/statistics/management/lib/gridCast";
|
||||
import { UpperCodeSelectDialog } from "@/components/statistics/management/UpperCodeSelectDialog";
|
||||
import type { ColumnInfo, GridViewWithEditCommit } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
DomesticPopulationLeftColumnConfig,
|
||||
DomesticPopulationRightColumnConfig,
|
||||
} from "@/components/util/column/management/DomesticPopulationColumn";
|
||||
import { useRef } from "react";
|
||||
|
||||
import { UpperCodeSelectDialog } from "@/components/statistics/management/UpperCodeSelectDialog";
|
||||
import type { usePopulationMappingTab } from "@/components/statistics/management/hooks/usePopulationMappingTab";
|
||||
import { asGridRowData, asGridRowSetter, fromGridRow } from "@/components/statistics/management/lib/gridCast";
|
||||
import type { DomesticPopulationItem } from "@/entities/statistics/model/ppltn/statistics-domestic-population-interface";
|
||||
|
||||
|
||||
const leftColumnInfo: ColumnInfo = {
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { Button } from "@common/components";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo, GridViewWithEditCommit } from "@/components/type/DataGrid.type";
|
||||
import { useRef } from "react";
|
||||
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { usePopulationMappingTab } from "@/components/statistics/management/hooks/usePopulationMappingTab";
|
||||
import { asGridRowData, asGridRowSetter, fromGridRow } from "@/components/statistics/management/lib/gridCast";
|
||||
import type { ForeignPopulationItem } from "@/components/statistics/management/types/population-tab-types";
|
||||
import { UpperCodeSelectDialog } from "@/components/statistics/management/UpperCodeSelectDialog";
|
||||
import type { ColumnInfo, GridViewWithEditCommit } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
ForeignPopulationLeftColumnConfig,
|
||||
ForeignPopulationRightColumnConfig,
|
||||
} from "@/components/util/column/management/ForeignPopulationColumn";
|
||||
|
||||
import { UpperCodeSelectDialog } from "@/components/statistics/management/UpperCodeSelectDialog";
|
||||
import type { usePopulationMappingTab } from "@/components/statistics/management/hooks/usePopulationMappingTab";
|
||||
import { asGridRowData, asGridRowSetter, fromGridRow } from "@/components/statistics/management/lib/gridCast";
|
||||
import type { ForeignPopulationItem } from "@/components/statistics/management/types/population-tab-types";
|
||||
|
||||
const leftColumnInfo: ColumnInfo = {
|
||||
columns: ForeignPopulationLeftColumnConfig.columns ?? [],
|
||||
columnLayout: ForeignPopulationLeftColumnConfig.columnLayout ?? [],
|
||||
|
||||
@@ -130,9 +130,9 @@ const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
|
||||
};
|
||||
|
||||
const getSggListFetcher = async (
|
||||
key: [string, string],
|
||||
key: [string, string, "Y" | "N"],
|
||||
): Promise<ApiResponse<Sgg[]>> => {
|
||||
return statisticsApi.getSggList(key[1]);
|
||||
return statisticsApi.getSggList(key[1], { gugunSkipYn: key[2] });
|
||||
};
|
||||
|
||||
const LogHistory = () => {
|
||||
@@ -214,8 +214,14 @@ const LogHistory = () => {
|
||||
},
|
||||
);
|
||||
|
||||
const sggKey: [string, string] | null =
|
||||
sido && sido !== "ALL" ? ["log-history-sgg-list", sido] : null;
|
||||
const selectedSido = sidoList?.data?.sidoList?.find(
|
||||
(item) => item.sido === sido,
|
||||
);
|
||||
const gugunSkipYn = selectedSido?.gugunSkipYn ?? "Y";
|
||||
const sggKey: [string, string, "Y" | "N"] | null =
|
||||
sido && sido !== "ALL" && sidoList?.data
|
||||
? ["log-history-sgg-list", sido, gugunSkipYn]
|
||||
: null;
|
||||
const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
|
||||
sggKey,
|
||||
getSggListFetcher,
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { GridFitStyle } from "realgrid";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { UserApi } from "@/entities/user";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -14,6 +18,8 @@ import {
|
||||
LoadingWithAnimation,
|
||||
} from "@common/components";
|
||||
|
||||
import { LogHistoryDetailsNestedDialog } from "./LogHistoryDetailsNestedDialog";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type {
|
||||
ColumnInfo,
|
||||
@@ -25,15 +31,10 @@ import {
|
||||
emptyLogHistoryDetailColumnInfo,
|
||||
LOG_DETAIL_DETAILS_FIELD,
|
||||
LOG_DETAIL_DETAILS_RAW_FIELD,
|
||||
normalizeLogDetailList,
|
||||
type LogDetailVerificationItem,
|
||||
normalizeLogDetailList,
|
||||
prepareLogDetailRowsForGrid,
|
||||
} from "@/components/util/column/management/LogHistoryDetailColumn";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { UserApi } from "@/entities/user";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
import { LogHistoryDetailsNestedDialog } from "./LogHistoryDetailsNestedDialog";
|
||||
|
||||
export interface LogHistoryDetailDialogProps {
|
||||
open: boolean;
|
||||
@@ -74,9 +75,9 @@ export function LogHistoryDetailDialog({
|
||||
if (!row) return;
|
||||
|
||||
const sn = row.statsLogSn;
|
||||
const rgtrId =
|
||||
row.rgtrId != null && String(row.rgtrId).trim() !== ""
|
||||
? String(row.rgtrId).trim()
|
||||
const rgtrUuid =
|
||||
row.rgtrUuid != null && String(row.rgtrUuid).trim() !== ""
|
||||
? String(row.rgtrUuid).trim()
|
||||
: "";
|
||||
let cancelled = false;
|
||||
|
||||
@@ -87,8 +88,8 @@ export function LogHistoryDetailDialog({
|
||||
|
||||
try {
|
||||
const userLookupPromise =
|
||||
rgtrId !== ""
|
||||
? UserApi.findByUserUnqId(rgtrId).catch(() => {
|
||||
rgtrUuid !== ""
|
||||
? UserApi.findByUserUnqId(rgtrUuid).catch(() => {
|
||||
return null;
|
||||
})
|
||||
: Promise.resolve(null);
|
||||
|
||||
@@ -48,7 +48,7 @@ export function LogHistoryDetailsNestedDialog({
|
||||
<span className="font-semibold">
|
||||
조합순번:
|
||||
</span>{" "}
|
||||
{item.eqtnCombSeq ?? "-"}
|
||||
{item.fmlaCombSeq ?? "-"}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">
|
||||
@@ -60,7 +60,7 @@ export function LogHistoryDetailsNestedDialog({
|
||||
<span className="font-semibold">
|
||||
비교연산:
|
||||
</span>{" "}
|
||||
{item.cmprOpert ?? "-"}
|
||||
{item.cmprArtopnCn ?? "-"}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">
|
||||
@@ -72,13 +72,13 @@ export function LogHistoryDetailsNestedDialog({
|
||||
<span className="font-semibold">
|
||||
한글 수식:
|
||||
</span>{" "}
|
||||
{item.kornArtopnEqtnCn ?? "-"}
|
||||
{item.kornArtopnFmlaCn ?? "-"}
|
||||
</p>
|
||||
<p className="col-span-2">
|
||||
<span className="font-semibold">
|
||||
영문 수식:
|
||||
</span>{" "}
|
||||
{item.engArtopnEqtnCn ?? "-"}
|
||||
{item.engArtopnFmlaCn ?? "-"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,8 @@ import { GridFitStyle } from "realgrid";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
UnSubmitDataStatusItem,
|
||||
unSubmitDataRequest,
|
||||
UnSubmitDataStatusItem,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
@@ -279,7 +279,6 @@ const NotEnteredStatus = () => {
|
||||
return;
|
||||
}
|
||||
const params = buildUnSubmitDataRequest(values, userInfo);
|
||||
console.log(params);
|
||||
if (!params.dtlInstCd) {
|
||||
alert("기관 정보를 확인할 수 없습니다.");
|
||||
return;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { Chart } from "react-chartjs-2";
|
||||
|
||||
import type { ChartOptions } from "chart.js";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarController,
|
||||
BarElement,
|
||||
CategoryScale,
|
||||
Chart as ChartJS,
|
||||
Legend,
|
||||
LinearScale,
|
||||
LineController,
|
||||
LineElement,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
import type { ChartOptions } from "chart.js";
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import { PopulationStatusColumnConfig } from "@/components/util/column/management/PopulationStatusColumn";
|
||||
|
||||
import type { usePopulationStatusTab } from "@/components/statistics/management/hooks/usePopulationStatusTab";
|
||||
import { asGridRowData, asGridRowSetter } from "@/components/statistics/management/lib/gridCast";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import { PopulationStatusColumnConfig } from "@/components/util/column/management/PopulationStatusColumn";
|
||||
|
||||
const columnInfo: ColumnInfo = {
|
||||
columns: PopulationStatusColumnConfig.columns ?? [],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import PopulationStatusChart from "./PopulationStatusChart";
|
||||
|
||||
import type { usePopulationVizTab } from "@/components/statistics/management/hooks/usePopulationVizTab";
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -20,14 +20,14 @@ import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
/** API `YYYYMMDD` 등 → Date */
|
||||
function seTypeToDisplayLabel(seType: StatsInputPeriodItem["seType"]): string {
|
||||
if (seType === "INPT") return "입력";
|
||||
if (seType === "VRFC") return "검증";
|
||||
return seType;
|
||||
function seTypeToDisplayLabel(seTypeCd: StatsInputPeriodItem["seTypeCd"]): string {
|
||||
if (seTypeCd === "INPT") return "입력";
|
||||
if (seTypeCd === "VRFC") return "검증";
|
||||
return seTypeCd;
|
||||
}
|
||||
|
||||
function rowSeTypeToCode(row: Record<string, string | number>): "INPT" | "VRFC" {
|
||||
const v = String(row.seType ?? "");
|
||||
const v = String(row.seTypeCd ?? "");
|
||||
if (v === "INPT" || v === "입력") return "INPT";
|
||||
if (v === "VRFC" || v === "검증") return "VRFC";
|
||||
return "INPT";
|
||||
@@ -86,16 +86,16 @@ const StatisticsInputManagement = () => {
|
||||
const params: StatsInputPeriodListRequest = {
|
||||
statsYr: values.year,
|
||||
...(values.clsfCd && values.clsfCd !== "ALL"
|
||||
? { seType: values.clsfCd }
|
||||
? { seTypeCd: values.clsfCd }
|
||||
: {}),
|
||||
};
|
||||
const res = await statisticsApi.getStatsInputPeriodList(params);
|
||||
if (res.status === "success" && Array.isArray(res.data)) {
|
||||
const rowsForGrid = res.data.map((row) => ({
|
||||
...row,
|
||||
seType: seTypeToDisplayLabel(row.seType),
|
||||
inptBgngYmd: formatYmdForDisplay(row.inptBgngYmd),
|
||||
inptEndYmd: formatYmdForDisplay(row.inptEndYmd),
|
||||
seTypeCd: seTypeToDisplayLabel(row.seTypeCd),
|
||||
bgngYmd: formatYmdForDisplay(row.bgngYmd),
|
||||
endYmd: formatYmdForDisplay(row.endYmd),
|
||||
[PERIOD_DETAIL_FIELD]: "상세정보",
|
||||
}));
|
||||
setRowData(toGridRows(rowsForGrid));
|
||||
@@ -115,6 +115,14 @@ const StatisticsInputManagement = () => {
|
||||
setRowData([]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const defaultYear = String(new Date().getFullYear() - 1);
|
||||
void handleSearch({
|
||||
year: defaultYear,
|
||||
clsfCd: "ALL",
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleRegisterClick = () => {
|
||||
const yr =
|
||||
searchFormValues?.year ?? lastSearchRef.current?.year ?? "";
|
||||
@@ -130,22 +138,22 @@ const StatisticsInputManagement = () => {
|
||||
|
||||
const openPeriodDetailForRow = useCallback(
|
||||
async (row: Record<string, string | number>) => {
|
||||
const seCdRaw = row.seCd;
|
||||
const seCdNum =
|
||||
typeof seCdRaw === "number"
|
||||
? seCdRaw
|
||||
: parseInt(String(seCdRaw ?? ""), 10);
|
||||
const seCd = Number.isFinite(seCdNum) ? seCdNum : 0;
|
||||
const cyclSeNoRaw = row.cyclSeNo;
|
||||
const cyclSeNoNum =
|
||||
typeof cyclSeNoRaw === "number"
|
||||
? cyclSeNoRaw
|
||||
: parseInt(String(cyclSeNoRaw ?? ""), 10);
|
||||
const cyclSeNo = Number.isFinite(cyclSeNoNum) ? cyclSeNoNum : 0;
|
||||
const statsYr = String(row.statsYr ?? "");
|
||||
const seType = rowSeTypeToCode(row);
|
||||
const seTypeCd = rowSeTypeToCode(row);
|
||||
const extSchdlId = parseExtSchdlId(row.extSchdlId);
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await statisticsApi.getStatsInputPeriodDetail({
|
||||
statsYr,
|
||||
seType,
|
||||
seCd,
|
||||
seTypeCd,
|
||||
cyclSeNo,
|
||||
...(extSchdlId != null ? { extSchdlId } : {}),
|
||||
});
|
||||
|
||||
@@ -160,13 +168,13 @@ const StatisticsInputManagement = () => {
|
||||
setPeriodDialogInitial({
|
||||
extSchdlId: detailExtSchdlId,
|
||||
statsYr: String(detail.statsYr ?? statsYr),
|
||||
seType: detail.seType === "VRFC" ? "VRFC" : "INPT",
|
||||
seCd:
|
||||
typeof detail.seCd === "number" && Number.isFinite(detail.seCd)
|
||||
? detail.seCd
|
||||
: seCd,
|
||||
inptBgngYmd: parseYmdToDate(detail.inptBgngYmd),
|
||||
inptEndYmd: parseYmdToDate(detail.inptEndYmd),
|
||||
seTypeCd: detail.seTypeCd === "VRFC" ? "VRFC" : "INPT",
|
||||
cyclSeNo:
|
||||
typeof detail.cyclSeNo === "number" && Number.isFinite(detail.cyclSeNo)
|
||||
? detail.cyclSeNo
|
||||
: cyclSeNo,
|
||||
bgngYmd: parseYmdToDate(detail.bgngYmd),
|
||||
endYmd: parseYmdToDate(detail.endYmd),
|
||||
});
|
||||
setPeriodDialogOpen(true);
|
||||
} catch {
|
||||
@@ -194,20 +202,20 @@ const StatisticsInputManagement = () => {
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "seType",
|
||||
fieldName: "seType",
|
||||
name: "seTypeCd",
|
||||
fieldName: "seTypeCd",
|
||||
header: { text: "분류" },
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "inptBgngYmd",
|
||||
fieldName: "inptBgngYmd",
|
||||
name: "bgngYmd",
|
||||
fieldName: "bgngYmd",
|
||||
header: { text: "시작일" },
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "inptEndYmd",
|
||||
fieldName: "inptEndYmd",
|
||||
name: "endYmd",
|
||||
fieldName: "endYmd",
|
||||
header: { text: "종료일" },
|
||||
editable: false,
|
||||
},
|
||||
@@ -223,9 +231,9 @@ const StatisticsInputManagement = () => {
|
||||
|
||||
const columnLayout = [
|
||||
"statsYr",
|
||||
"seType",
|
||||
"inptBgngYmd",
|
||||
"inptEndYmd",
|
||||
"seTypeCd",
|
||||
"bgngYmd",
|
||||
"endYmd",
|
||||
PERIOD_DETAIL_FIELD,
|
||||
];
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ import { getUserInfo } from "../../../../../../packages/common/components/src/ap
|
||||
export type StatisticsInputPeriodDialogValues = {
|
||||
extSchdlId?: number;
|
||||
statsYr: string;
|
||||
seType: "INPT" | "VRFC";
|
||||
seCd: number;
|
||||
inptBgngYmd: Date | undefined;
|
||||
inptEndYmd: Date | undefined;
|
||||
seTypeCd: "INPT" | "VRFC";
|
||||
cyclSeNo: number;
|
||||
bgngYmd: Date | undefined;
|
||||
endYmd: Date | undefined;
|
||||
};
|
||||
|
||||
export type StatisticsInputPeriodDialogMode = "edit" | "create";
|
||||
@@ -76,33 +76,33 @@ export function StatisticsInputPeriodDialog({
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const [statsYr, setStatsYr] = useState("");
|
||||
const [seType, setSeType] = useState<"INPT" | "VRFC" | null>(null);
|
||||
const [seCdInput, setSeCdInput] = useState("");
|
||||
const [inptBgngYmd, setInptBgngYmd] = useState<Date | undefined>(undefined);
|
||||
const [inptEndYmd, setInptEndYmd] = useState<Date | undefined>(undefined);
|
||||
const [seTypeCd, setSeTypeCd] = useState<"INPT" | "VRFC" | null>(null);
|
||||
const [cyclSeNoInput, setCyclSeNoInput] = useState("");
|
||||
const [bgngYmd, setBgngYmd] = useState<Date | undefined>(undefined);
|
||||
const [endYmd, setEndYmd] = useState<Date | undefined>(undefined);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (mode === "create") {
|
||||
setStatsYr(normalizeStatsYrInput(createDefaultStatsYr ?? ""));
|
||||
setSeType(null);
|
||||
setSeCdInput("0");
|
||||
setInptBgngYmd(undefined);
|
||||
setInptEndYmd(undefined);
|
||||
setSeTypeCd(null);
|
||||
setCyclSeNoInput("0");
|
||||
setBgngYmd(undefined);
|
||||
setEndYmd(undefined);
|
||||
return;
|
||||
}
|
||||
if (!initial) return;
|
||||
setStatsYr(normalizeStatsYrInput(String(initial.statsYr ?? "")));
|
||||
setSeType(initial.seType);
|
||||
setSeCdInput(String(Number.isFinite(initial.seCd) ? initial.seCd : 0));
|
||||
setInptBgngYmd(initial.inptBgngYmd);
|
||||
setInptEndYmd(initial.inptEndYmd);
|
||||
setSeTypeCd(initial.seTypeCd);
|
||||
setCyclSeNoInput(String(Number.isFinite(initial.cyclSeNo) ? initial.cyclSeNo : 0));
|
||||
setBgngYmd(initial.bgngYmd);
|
||||
setEndYmd(initial.endYmd);
|
||||
}, [open, mode, initial, createDefaultStatsYr]);
|
||||
|
||||
const handleSave = async () => {
|
||||
const bgng = formatDateToYmd(inptBgngYmd);
|
||||
const end = formatDateToYmd(inptEndYmd);
|
||||
const bgng = formatDateToYmd(bgngYmd);
|
||||
const end = formatDateToYmd(endYmd);
|
||||
if (!bgng || !end) {
|
||||
alert("시작일과 종료일을 선택해주세요.");
|
||||
return;
|
||||
@@ -111,18 +111,18 @@ export function StatisticsInputPeriodDialog({
|
||||
alert("시작일은 종료일보다 늦을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
if (seType === null) {
|
||||
if (seTypeCd === null) {
|
||||
alert("분류를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
const seCdParsed =
|
||||
const cyclSeNoParsed =
|
||||
mode === "create"
|
||||
? 0
|
||||
: (() => {
|
||||
const n = parseInt(seCdInput, 10);
|
||||
const n = parseInt(cyclSeNoInput, 10);
|
||||
return Number.isFinite(n) && n >= 0 ? n : NaN;
|
||||
})();
|
||||
if (mode === "edit" && !Number.isFinite(seCdParsed)) {
|
||||
if (mode === "edit" && !Number.isFinite(cyclSeNoParsed)) {
|
||||
alert("구분 값이 올바르지 않습니다.");
|
||||
return;
|
||||
}
|
||||
@@ -140,13 +140,13 @@ export function StatisticsInputPeriodDialog({
|
||||
const userInfo = await getUserInfo();
|
||||
const param: StatsInputPeriodSaveRequest = {
|
||||
statsYr: yr,
|
||||
seType,
|
||||
inptBgngYmd: bgng,
|
||||
inptEndYmd: end,
|
||||
rgtrId: userInfo.userUnqId,
|
||||
seTypeCd,
|
||||
bgngYmd: bgng,
|
||||
endYmd: end,
|
||||
rgtrUuid: userInfo.userUnqId,
|
||||
};
|
||||
if (!isCreate) {
|
||||
param.seCd = seCdParsed;
|
||||
param.cyclSeNo = cyclSeNoParsed;
|
||||
if (
|
||||
initial?.extSchdlId != null &&
|
||||
Number.isFinite(initial.extSchdlId)
|
||||
@@ -173,8 +173,8 @@ export function StatisticsInputPeriodDialog({
|
||||
if (mode === "create" || !initial) return;
|
||||
if (!(await confirm("삭제하시겠습니까?"))) return;
|
||||
|
||||
const bgng = formatDateToYmd(initial.inptBgngYmd);
|
||||
const end = formatDateToYmd(initial.inptEndYmd);
|
||||
const bgng = formatDateToYmd(initial.bgngYmd);
|
||||
const end = formatDateToYmd(initial.endYmd);
|
||||
if (!bgng || !end) {
|
||||
alert("삭제할 기간 정보가 없습니다.");
|
||||
return;
|
||||
@@ -184,19 +184,19 @@ export function StatisticsInputPeriodDialog({
|
||||
alert("연도가 없습니다.");
|
||||
return;
|
||||
}
|
||||
const seCd =
|
||||
Number.isFinite(initial.seCd) && initial.seCd >= 0 ? initial.seCd : 0;
|
||||
const cyclSeNo =
|
||||
Number.isFinite(initial.cyclSeNo) && initial.cyclSeNo >= 0 ? initial.cyclSeNo : 0;
|
||||
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const param: StatsInputPeriodDeleteRequest = {
|
||||
statsYr: yr,
|
||||
seType: initial.seType,
|
||||
seCd,
|
||||
inptBgngYmd: bgng,
|
||||
inptEndYmd: end,
|
||||
rgtrId: userInfo.userUnqId,
|
||||
seTypeCd: initial.seTypeCd,
|
||||
cyclSeNo,
|
||||
bgngYmd: bgng,
|
||||
endYmd: end,
|
||||
rgtrUuid: userInfo.userUnqId,
|
||||
};
|
||||
if (initial.extSchdlId != null && Number.isFinite(initial.extSchdlId)) {
|
||||
param.extSchdlId = initial.extSchdlId;
|
||||
@@ -253,8 +253,8 @@ export function StatisticsInputPeriodDialog({
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-label">분류</Label>
|
||||
<Select
|
||||
value={seType ?? undefined}
|
||||
onValueChange={(v) => setSeType(v as "INPT" | "VRFC")}
|
||||
value={seTypeCd ?? undefined}
|
||||
onValueChange={(v) => setSeTypeCd(v as "INPT" | "VRFC")}
|
||||
disabled={isSubmitting || mode === "edit"}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
@@ -273,7 +273,7 @@ export function StatisticsInputPeriodDialog({
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
min={0}
|
||||
value={seCdInput}
|
||||
value={cyclSeNoInput}
|
||||
disabled
|
||||
className="!text-[1.3rem]"
|
||||
/>
|
||||
@@ -282,16 +282,16 @@ export function StatisticsInputPeriodDialog({
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-label">시작일</Label>
|
||||
<InputDatePicker
|
||||
value={inptBgngYmd}
|
||||
onChange={setInptBgngYmd}
|
||||
value={bgngYmd}
|
||||
onChange={setBgngYmd}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-label">종료일</Label>
|
||||
<InputDatePicker
|
||||
value={inptEndYmd}
|
||||
onChange={setInptEndYmd}
|
||||
value={endYmd}
|
||||
onChange={setEndYmd}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { Button, LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { statisticsPpltnApi } from "@/entities/statistics/api/statistics-ppltn-service";
|
||||
import type { DomesticPopulationItem } from "@/entities/statistics/model/ppltn/statistics-domestic-population-interface";
|
||||
import { Button, LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import { usePopulationMappingTab } from "@/components/statistics/management/hooks/usePopulationMappingTab";
|
||||
import { useAdmdstRenewalTab } from "@/components/statistics/management/hooks/useAdmdstRenewalTab";
|
||||
import { usePopulationStatusTab } from "@/components/statistics/management/hooks/usePopulationStatusTab";
|
||||
import { usePopulationVizTab } from "@/components/statistics/management/hooks/usePopulationVizTab";
|
||||
|
||||
import AdmdstRenewalTab from "./AdmdstRenewalTab";
|
||||
import DomesticPopulationTab from "./DomesticPopulationTab";
|
||||
import ForeignPopulationTab from "./ForeignPopulationTab";
|
||||
import AdmdstRenewalTab from "./AdmdstRenewalTab";
|
||||
import PopulationStatusTab from "./PopulationStatusTab";
|
||||
import PopulationVizTab from "./PopulationVizTab";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { useAdmdstRenewalTab } from "@/components/statistics/management/hooks/useAdmdstRenewalTab";
|
||||
import { usePopulationMappingTab } from "@/components/statistics/management/hooks/usePopulationMappingTab";
|
||||
import { usePopulationStatusTab } from "@/components/statistics/management/hooks/usePopulationStatusTab";
|
||||
import { usePopulationVizTab } from "@/components/statistics/management/hooks/usePopulationVizTab";
|
||||
import type { ForeignPopulationItem } from "@/components/statistics/management/types/population-tab-types";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
|
||||
const TAB_LIST = [
|
||||
{ key: "domestic", label: "행정인구등록" },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsPpltnApi } from "@/entities/statistics/api/statistics-ppltn-service";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import type { UpperCodeCandidateItem } from "@/entities/statistics/model/ppltn/statistics-domestic-population-interface";
|
||||
|
||||
// TODO: 원본 파일은 `../../../../../../packages/common/components/src/api` (6단계 상위) 였음.
|
||||
// 이 훅 파일은 원본 컴포넌트보다 한 단계 더 깊은 폴더(hooks/)에 위치하므로
|
||||
// 실제 프로젝트에 옮길 때 상대경로 깊이를 다시 맞춰줘야 함 (혹은 @common/components에 getUserInfo가
|
||||
@@ -10,13 +11,14 @@ import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { getUserInfo } from "../../../../../../../packages/common/components/src/api";
|
||||
|
||||
import { useAsyncAction } from "./useAsyncAction";
|
||||
import type { UpperCodeCandidateItem } from "@/entities/statistics/model/ppltn/statistics-domestic-population-interface";
|
||||
|
||||
import type {
|
||||
AdmdstRenewalAddedItem,
|
||||
AdmdstRenewalAddedSaveItem,
|
||||
AdmdstRenewalReducedItem,
|
||||
AdmdstRenewalReducedSaveItem,
|
||||
} from "@/components/statistics/management/types/population-tab-types";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
export function useAdmdstRenewalTab() {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
import { useAsyncAction } from "./useAsyncAction";
|
||||
|
||||
import type {
|
||||
PopulationMappingApiConfig,
|
||||
PopulationMappingRow,
|
||||
UpperCodeCandidateItem,
|
||||
} from "@/components/statistics/management/types/population-tab-types";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
/* ── 엑셀 헤더 검증 (원본 컴포넌트의 validateExcelHeader를 그대로 이동) ── */
|
||||
const validateExcelHeader = (file: File, requiredKeywords: string[]): Promise<boolean> => {
|
||||
|
||||
@@ -2,10 +2,12 @@ import { useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsPpltnApi } from "@/entities/statistics/api/statistics-ppltn-service";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
import type { PopulationStatusItem } from "../types/population-tab-types";
|
||||
|
||||
import { useAsyncAction } from "./useAsyncAction";
|
||||
import type { PopulationStatusItem } from "../types/population-tab-types";
|
||||
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
export function usePopulationStatusTab() {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
@@ -3,9 +3,10 @@ import { useState } from "react";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsPpltnApi } from "@/entities/statistics/api/statistics-ppltn-service";
|
||||
|
||||
import { useAsyncAction } from "./useAsyncAction";
|
||||
import type { PopulationChartData } from "../PopulationStatusChart";
|
||||
|
||||
import { useAsyncAction } from "./useAsyncAction";
|
||||
|
||||
export function usePopulationVizTab() {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { isLoading, run } = useAsyncAction(alert);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ApiResponse } from "@/entities/statistics/model/statistics-interface";
|
||||
import type { UpperCodeCandidateItem } from "@/entities/statistics/model/ppltn/statistics-domestic-population-interface";
|
||||
import type { ApiResponse } from "@/entities/statistics/model/statistics-interface";
|
||||
|
||||
/* ══════════════════════════════════════════════════
|
||||
외국인 인구 관련 타입
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ExcretaTreatmentFacilityReviewItem,
|
||||
} from "@/entities/statistics/model/statistics-excreta-treatment-facility-review-interface";
|
||||
@@ -11,28 +10,29 @@ import {
|
||||
extractExcretaTreatmentFacilityReviewList,
|
||||
extractExcretaTreatmentFacilityReviewStatsStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-excreta-treatment-facility-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewOpinionSaveRequest,
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { ExcretaTreatmentFacilityInfoList } from "@/components/util/column/review/ExcretaTreatmentFacilityRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const excretaTreatmentFacilityTab = ExcretaTreatmentFacilityInfoList[0];
|
||||
|
||||
@@ -76,11 +76,11 @@ const ExcretaTreatmentFacilityRvw = () => {
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -150,16 +150,16 @@ const ExcretaTreatmentFacilityRvw = () => {
|
||||
...item,
|
||||
admdstNm: item.admdstNm ?? "",
|
||||
fcltPstnCn: item.fcltPstnCn ?? "",
|
||||
telNo: item.telNo ?? "",
|
||||
telnoCn: item.telnoCn ?? "",
|
||||
cmptnStrtYmd: item.cmptnStrtYmd ?? "",
|
||||
oprtngStrtYmd: item.oprtngStrtYmd ?? "",
|
||||
prcsMthdExpln: item.prcsMthdExpln ?? "",
|
||||
prcsExpln: item.prcsExpln ?? "",
|
||||
linkTmplntCn: item.linkTmplntCn ?? "",
|
||||
wtsysNm: item.wtsysNm ?? "",
|
||||
trbtrNm: item.trbtrNm ?? "",
|
||||
fcltWtsysNm: item.fcltWtsysNm ?? "",
|
||||
fcltTrbtrNm: item.fcltTrbtrNm ?? "",
|
||||
msirbCn: item.msirbCn ?? "",
|
||||
ssirbCn: item.ssirbCn ?? "",
|
||||
operMthdCdCn: item.operMthdCdCn ?? "",
|
||||
operCn: item.operCn ?? "",
|
||||
rmrk: item.rmrk ?? "",
|
||||
}));
|
||||
|
||||
@@ -170,7 +170,7 @@ const ExcretaTreatmentFacilityRvw = () => {
|
||||
statsCd: statsInfo.statsCd ?? "",
|
||||
});
|
||||
setLatestReviewOpinion(statsInfo.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(statsInfo.wrterOpnnCn ?? "");
|
||||
setLatestProcessHistory(statsInfo.splmntRsltCn ?? "");
|
||||
setRowData(toGridRows(convertedData as unknown as object[]));
|
||||
} else {
|
||||
setRowData([]);
|
||||
@@ -216,7 +216,7 @@ const ExcretaTreatmentFacilityRvw = () => {
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.wrterOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.splmntRsltCn ?? "");
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
@@ -234,10 +234,10 @@ const ExcretaTreatmentFacilityRvw = () => {
|
||||
setStatsStatusInfo((prev) => ({
|
||||
...prev,
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
}));
|
||||
setLatestReviewOpinion(param.splmntOpnnCn);
|
||||
setLatestProcessHistory(param.wrterOpnnCn);
|
||||
setLatestProcessHistory(param.splmntRsltCn);
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
extractPrivateSewageFacilityReviewList,
|
||||
extractPrivateSewageFacilityReviewStatsStatusInfo,
|
||||
@@ -10,29 +14,25 @@ import {
|
||||
type PrivateSewageFacilityReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-private-sewage-facility-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { PrivateSewageFacilityInfoListRvw } from "@/components/util/column/review/PrivateSewageFacilityColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = PrivateSewageFacilityInfoListRvw[0]?.tabKey ?? "ST00080001";
|
||||
|
||||
@@ -102,7 +102,7 @@ const PrivateSewageFacilityRvw = () => {
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
@@ -127,11 +127,11 @@ const PrivateSewageFacilityRvw = () => {
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -284,7 +284,7 @@ const PrivateSewageFacilityRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
splmntRsltCn: row?.splmntRsltCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
@@ -307,7 +307,7 @@ const PrivateSewageFacilityRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { PublicSewageFacilityReviewItem } from "@/entities/statistics/model/statistics-public-sewage-facility-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewOpinionSaveRequest,
|
||||
ReviewerInfoRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { PublicSewageFacilityInfoList } from "@/components/util/column/review/PublicSewageFacilityRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
import { PublicSewageFacilityInfoList } from "@/components/util/column/review/PublicSewageFacilityRvwColumn";
|
||||
|
||||
// 하수도 통계 관리 06.공공하수처리시설
|
||||
const PublicSewageFacilityRvw = () => {
|
||||
@@ -63,8 +63,8 @@ const PublicSewageFacilityRvw = () => {
|
||||
);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const submit = statsStatusInfo.submitStatusCd != null ? String(statsStatusInfo.submitStatusCd) : "";
|
||||
const prvn = statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submit = statsStatusInfo.lclgvSbmsnSttsCd != null ? String(statsStatusInfo.lclgvSbmsnSttsCd) : "";
|
||||
const prvn = statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -144,7 +144,7 @@ const PublicSewageFacilityRvw = () => {
|
||||
response.data.statsStatusInfo.splmntOpnnCn ?? "",
|
||||
);
|
||||
setLatestProcessHistory(
|
||||
response.data.statsStatusInfo.wrterOpnnCn ?? "",
|
||||
response.data.statsStatusInfo.splmntRsltCn ?? "",
|
||||
);
|
||||
setRowData(toGridRows(convertedData as unknown as object[]));
|
||||
} else {
|
||||
@@ -192,7 +192,7 @@ const PublicSewageFacilityRvw = () => {
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.wrterOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.splmntRsltCn ?? "");
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
@@ -210,10 +210,10 @@ const PublicSewageFacilityRvw = () => {
|
||||
setStatsStatusInfo((prev) => ({
|
||||
...prev,
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
}));
|
||||
setLatestReviewOpinion(param.splmntOpnnCn);
|
||||
setLatestProcessHistory(param.wrterOpnnCn);
|
||||
setLatestProcessHistory(param.splmntRsltCn);
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
extractMediationPumpStationList,
|
||||
extractMediationPumpStationStatsStatusInfo,
|
||||
type MediationPumpStationItem,
|
||||
} from "@/entities/statistics/model/statistics-pumping-station-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewOpinionSaveRequest,
|
||||
ReviewerInfoRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { PumpingStationInfoList } from "@/components/util/column/review/PumpingStationRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
/**
|
||||
* 하수도 통계 검토 05.중계펌프장
|
||||
@@ -71,11 +71,11 @@ const PumpingStationRvw = () => {
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -144,12 +144,9 @@ const PumpingStationRvw = () => {
|
||||
const convertedData = rawList.map((item: MediationPumpStationItem) => ({
|
||||
...item,
|
||||
admdstNm: item.admdstNm ?? "",
|
||||
pumpstNm: item.fcltNm ?? "",
|
||||
impdFcltPstnCn: item.fcltPstnCn ?? "",
|
||||
pumpSeNm: item.pumpSeNm ?? "",
|
||||
swtrmtFcltNm: item.swtrmtFcltNm ?? "",
|
||||
oprtngStrtYmd: item.oprtngStrtYmd ?? "",
|
||||
exlnPumpN4RsrvCnt: item.exlnPumpN1RsrvCnt ?? 0,
|
||||
rmrk: item.rmrk ?? "",
|
||||
}));
|
||||
|
||||
@@ -160,7 +157,7 @@ const PumpingStationRvw = () => {
|
||||
statsCd: statsInfo.statsCd ?? "",
|
||||
});
|
||||
setLatestReviewOpinion(statsInfo.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(statsInfo.wrterOpnnCn ?? "");
|
||||
setLatestProcessHistory(statsInfo.splmntRsltCn ?? "");
|
||||
setRowData(toGridRows(convertedData));
|
||||
} else {
|
||||
setRowData([]);
|
||||
@@ -206,7 +203,7 @@ const PumpingStationRvw = () => {
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.wrterOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.splmntRsltCn ?? "");
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
@@ -224,10 +221,10 @@ const PumpingStationRvw = () => {
|
||||
setStatsStatusInfo((prev) => ({
|
||||
...prev,
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
}));
|
||||
setLatestReviewOpinion(param.splmntOpnnCn);
|
||||
setLatestProcessHistory(param.wrterOpnnCn);
|
||||
setLatestProcessHistory(param.splmntRsltCn);
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { ReviewReturnSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -16,9 +19,7 @@ import {
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type { ReviewReturnSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
export type ReturnReasonDialogContext = Pick<
|
||||
ReviewReturnSaveRequest,
|
||||
@@ -95,7 +96,7 @@ export function ReturnReasonDialog({
|
||||
sewerBzmnCd: context.sewerBzmnCd,
|
||||
statsCd: context.statsCd,
|
||||
rjctOpnnCn: reason,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
};
|
||||
|
||||
const response = await statisticsApi.saveReviewReturn(param);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -13,10 +15,8 @@ import {
|
||||
Label,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
export type ReviewOpinionDialogContext = Pick<
|
||||
ReviewOpinionSaveRequest,
|
||||
@@ -96,14 +96,14 @@ export function ReviewOpinionDialog({
|
||||
sewerBzmnCd: context.sewerBzmnCd,
|
||||
statsCd: context.statsCd,
|
||||
splmntOpnnCn: reviewOpinion,
|
||||
wrterOpnnCn: processHistory,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
splmntRsltCn: processHistory,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
};
|
||||
await onSave?.(param);
|
||||
resetForm();
|
||||
onOpenChange(false);
|
||||
} catch {
|
||||
/* 저장 실패 시 부모에서 alert 후 throw — 다이얼로그는 유지 */
|
||||
console.error('검토 의견 저장 error');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewerInfoResponse,
|
||||
ReviewSubmitRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -16,19 +22,18 @@ import {
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewerInfoResponse,
|
||||
ReviewSubmitRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
export interface ReviewSubmitDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
context: ReviewerInfoRequest | null;
|
||||
onSubmitted?: () => void | Promise<void>;
|
||||
/**
|
||||
* true면 검토의견·처리내역 UI를 숨기고
|
||||
* submit body에 statsCd / splmntOpnnCn / splmntRsltCn을 넣지 않음
|
||||
*/
|
||||
hideOpinionFields?: boolean;
|
||||
}
|
||||
|
||||
export function ReviewSubmitDialog({
|
||||
@@ -36,6 +41,7 @@ export function ReviewSubmitDialog({
|
||||
onOpenChange,
|
||||
context,
|
||||
onSubmitted,
|
||||
hideOpinionFields = false,
|
||||
}: ReviewSubmitDialogProps) {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [reviewerInfo, setReviewerInfo] =
|
||||
@@ -98,14 +104,20 @@ export function ReviewSubmitDialog({
|
||||
setIsSubmitting(true);
|
||||
|
||||
const userInfo = await getUserInfo();
|
||||
const submitParam: ReviewSubmitRequest = {
|
||||
statsYr: context.statsYr,
|
||||
sewerBzmnCd: context.sewerBzmnCd,
|
||||
statsCd: context.statsCd,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
splmntOpnnCn: reviewerInfo?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: reviewerInfo?.wrterOpnnCn ?? "",
|
||||
};
|
||||
const submitParam: ReviewSubmitRequest = hideOpinionFields
|
||||
? {
|
||||
statsYr: context.statsYr,
|
||||
sewerBzmnCd: [context.sewerBzmnCd],
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
}
|
||||
: {
|
||||
statsYr: context.statsYr,
|
||||
sewerBzmnCd: context.sewerBzmnCd,
|
||||
statsCd: context.statsCd,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
splmntOpnnCn: reviewerInfo?.splmntOpnnCn ?? "",
|
||||
splmntRsltCn: reviewerInfo?.splmntRsltCn ?? "",
|
||||
};
|
||||
|
||||
const response = await statisticsApi.submitReview(submitParam);
|
||||
if (response.status !== "success") {
|
||||
@@ -153,22 +165,26 @@ export function ReviewSubmitDialog({
|
||||
<Label className="text-label">직책</Label>
|
||||
<Input value={reviewerInfo?.cps ?? "-"} readOnly />
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col gap-2">
|
||||
<Label className="text-label">검토의견</Label>
|
||||
<Textarea
|
||||
value={reviewerInfo?.splmntOpnnCn ?? "-"}
|
||||
readOnly
|
||||
className="min-h-[10rem] p-2 resize-none !text-xl"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col gap-2">
|
||||
<Label className="text-label">처리내역</Label>
|
||||
<Textarea
|
||||
value={reviewerInfo?.wrterOpnnCn ?? "-"}
|
||||
readOnly
|
||||
className="min-h-[10rem] p-2 resize-none !text-xl"
|
||||
/>
|
||||
</div>
|
||||
{!hideOpinionFields && (
|
||||
<>
|
||||
<div className="col-span-2 flex flex-col gap-2">
|
||||
<Label className="text-label">검토의견</Label>
|
||||
<Textarea
|
||||
value={reviewerInfo?.splmntOpnnCn ?? "-"}
|
||||
readOnly
|
||||
className="min-h-[10rem] p-2 resize-none !text-xl"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col gap-2">
|
||||
<Label className="text-label">처리내역</Label>
|
||||
<Textarea
|
||||
value={reviewerInfo?.splmntRsltCn ?? "-"}
|
||||
readOnly
|
||||
className="min-h-[10rem] p-2 resize-none !text-xl"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
|
||||
@@ -3,6 +3,10 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
extractSewageExcretaRelatedReviewList,
|
||||
extractSewageExcretaRelatedReviewStatsStatusInfo,
|
||||
@@ -10,29 +14,25 @@ import {
|
||||
type SewageExcretaRelatedReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-sewage-excreta-related-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewageExcretaRelatedInfoListRvw } from "@/components/util/column/review/SewageExcretaRelatedRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewageExcretaRelatedInfoListRvw[0]?.tabKey ?? "ST00120001";
|
||||
|
||||
@@ -105,7 +105,7 @@ const SewageExcretaRelatedRvw = () => {
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
@@ -130,11 +130,11 @@ const SewageExcretaRelatedRvw = () => {
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -289,7 +289,7 @@ const SewageExcretaRelatedRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
splmntRsltCn: row?.splmntRsltCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
@@ -312,7 +312,7 @@ const SewageExcretaRelatedRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -3,6 +3,10 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
extractSewageExcretaSlurgeReviewList,
|
||||
extractSewageExcretaSlurgeReviewStatsStatusInfo,
|
||||
@@ -10,29 +14,25 @@ import {
|
||||
type SewageExcretaSlurgeReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-sewage-excreta-slurge-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewageExcretaSludgeInfoListRvw } from "@/components/util/column/review/SewageExcretaSlurgeRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewageExcretaSludgeInfoListRvw[0]?.tabKey ?? "ST00110001";
|
||||
|
||||
@@ -105,7 +105,7 @@ const SewageExcretaSlurgeRvw = () => {
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
@@ -130,11 +130,11 @@ const SewageExcretaSlurgeRvw = () => {
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -290,7 +290,7 @@ const SewageExcretaSlurgeRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
splmntRsltCn: row?.splmntRsltCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
@@ -313,7 +313,7 @@ const SewageExcretaSlurgeRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -3,33 +3,33 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
SewerPipeMtcRequestParams,
|
||||
StatsStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type {
|
||||
SewerPipeMtcRequestParams,
|
||||
StatsStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewagePipeMaintainColumnInfoList[0]?.tabKey ?? "ST00030001";
|
||||
|
||||
@@ -120,7 +120,7 @@ const SewagePipeMaintainRvw = () => {
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
@@ -145,11 +145,11 @@ const SewagePipeMaintainRvw = () => {
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -302,7 +302,7 @@ const SewagePipeMaintainRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
splmntRsltCn: row?.splmntRsltCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
@@ -325,7 +325,7 @@ const SewagePipeMaintainRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -3,27 +3,28 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
SewagePipeReviewRequestParams,
|
||||
StatsStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type {
|
||||
SewagePipeReviewRequestParams,
|
||||
StatsStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type {
|
||||
ColumnConfig,
|
||||
ColumnInfo,
|
||||
@@ -31,10 +32,9 @@ import type {
|
||||
ColumnLayoutItem,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import { getST00020003RvwConfig,SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
|
||||
const firstTabKey = SewagePipeColumnInfoListRvw[0]?.tabKey ?? "ST00020001";
|
||||
|
||||
@@ -148,15 +148,23 @@ const SewagePipeRvw = () => {
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
const columnInfo = useMemo<ColumnInfoList[]>(
|
||||
() =>
|
||||
SewagePipeColumnInfoListRvw.map((tab) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col) => cloneColumnConfig(col)),
|
||||
columnLayout: (tab.columnLayout ?? []).map((item) =>
|
||||
cloneColumnLayoutItem(item),
|
||||
),
|
||||
})),
|
||||
[],
|
||||
() => {
|
||||
const statsYr = lastSearchValues?.year ?? new Date().getFullYear();
|
||||
const st00020003Config = getST00020003RvwConfig(statsYr);
|
||||
return SewagePipeColumnInfoListRvw.map((tab) => {
|
||||
const source =
|
||||
tab.tabKey === "ST00020003" ? st00020003Config
|
||||
: tab;
|
||||
return {
|
||||
...source,
|
||||
columns: (source.columns ?? []).map((col) => cloneColumnConfig(col as Parameters<typeof cloneColumnConfig>[0])),
|
||||
columnLayout: (source.columnLayout ?? []).map((item) =>
|
||||
cloneColumnLayoutItem(item as ColumnLayoutItem),
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
[lastSearchValues?.year],
|
||||
);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
@@ -173,7 +181,7 @@ const SewagePipeRvw = () => {
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
|
||||
@@ -197,11 +205,11 @@ const SewagePipeRvw = () => {
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -354,7 +362,7 @@ const SewagePipeRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
splmntRsltCn: row?.splmntRsltCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
@@ -377,7 +385,7 @@ const SewagePipeRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -3,31 +3,31 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type { SewerReservoirRequestParams } from "@/entities/statistics/model/sewage-reservoir-pump-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewageReservoirPumpInfoListRvw } from "@/components/util/column/review/SewageReservoirPumpRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewageReservoirPumpInfoListRvw[0]?.tabKey ?? "ST00040001";
|
||||
|
||||
@@ -126,7 +126,7 @@ const SewageReservoirPumpRvw = () => {
|
||||
}, [activeStatsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
@@ -151,11 +151,11 @@ const SewageReservoirPumpRvw = () => {
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -308,7 +308,7 @@ const SewageReservoirPumpRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
splmntRsltCn: row?.splmntRsltCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
@@ -331,7 +331,7 @@ const SewageReservoirPumpRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -3,36 +3,36 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import {
|
||||
extractSewerageFinanceReviewList,
|
||||
extractSewerageFinanceReviewStatsStatusInfo,
|
||||
mapSewerageFinanceReviewRows,
|
||||
type SewerageFinanceReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-sewerage-finance-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewerageFinanceInfoListRvw } from "@/components/util/column/review/SewerageFinanceRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewerageFinanceInfoListRvw[0]?.tabKey ?? "ST00090001";
|
||||
|
||||
@@ -105,7 +105,7 @@ const SewerageFinanceRvw = () => {
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
@@ -130,11 +130,11 @@ const SewerageFinanceRvw = () => {
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -287,7 +287,7 @@ const SewerageFinanceRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
splmntRsltCn: row?.splmntRsltCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
@@ -310,7 +310,7 @@ const SewerageFinanceRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
import { useState, useMemo } from "react";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import { useMemo,useState } from "react";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn";
|
||||
import type { SewerageSupplyRateReviewItem, statsStatusInfo } from "@/entities/statistics/model/statistics-sewerage-penetration-review-interface";
|
||||
import type {
|
||||
ReviewOpinionSaveRequest,
|
||||
ReviewerInfoRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
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";
|
||||
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { SewerageSupplyRateReviewItem, statsStatusInfo } from "@/entities/statistics/model/statistics-sewerage-penetration-review-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
|
||||
// 하수도 통계 검토 01.하수도 보급률
|
||||
const SeweragePenetrationRateRvw = () => {
|
||||
@@ -62,8 +63,8 @@ const SeweragePenetrationRateRvw = () => {
|
||||
);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const submit = statsStatusInfo.submitStatusCd != null ? String(statsStatusInfo.submitStatusCd) : "";
|
||||
const prvn = statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submit = statsStatusInfo.lclgvSbmsnSttsCd != null ? String(statsStatusInfo.lclgvSbmsnSttsCd) : "";
|
||||
const prvn = statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -139,7 +140,7 @@ const SeweragePenetrationRateRvw = () => {
|
||||
response.data.statsStatusInfo.splmntOpnnCn ?? "",
|
||||
);
|
||||
setLatestProcessHistory(
|
||||
response.data.statsStatusInfo.wrterOpnnCn ?? "",
|
||||
response.data.statsStatusInfo.splmntRsltCn ?? "",
|
||||
);
|
||||
setRowData(toGridRows(convertedData));
|
||||
} else {
|
||||
@@ -186,7 +187,7 @@ const SeweragePenetrationRateRvw = () => {
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.wrterOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.splmntRsltCn ?? "");
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
@@ -204,10 +205,10 @@ const SeweragePenetrationRateRvw = () => {
|
||||
setStatsStatusInfo((prev) => ({
|
||||
...prev,
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
}));
|
||||
setLatestReviewOpinion(param.splmntOpnnCn);
|
||||
setLatestProcessHistory(param.wrterOpnnCn);
|
||||
setLatestProcessHistory(param.splmntRsltCn);
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
@@ -3,30 +3,31 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import {
|
||||
extractSewerageServiceLevelReviewList,
|
||||
extractSewerageServiceLevelReviewStatsStatusInfo,
|
||||
mapSewerageServiceLevelReviewRows,
|
||||
type SewerageServiceLevelReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-sewerage-service-level-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type {
|
||||
ColumnConfig,
|
||||
ColumnInfo,
|
||||
@@ -36,7 +37,6 @@ import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewerageServiceLevelInfoListRvw } from "@/components/util/column/review/SewerageServiceLevelRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewerageServiceLevelInfoListRvw[0]?.tabKey ?? "ST00130001";
|
||||
|
||||
@@ -103,7 +103,7 @@ const SewerageServiceLevelRvw = () => {
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
@@ -128,11 +128,11 @@ const SewerageServiceLevelRvw = () => {
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -288,7 +288,7 @@ const SewerageServiceLevelRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
splmntRsltCn: row?.splmntRsltCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
@@ -311,7 +311,7 @@ const SewerageServiceLevelRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -1,36 +1,42 @@
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { useAlertStore, useSearchConditionSidoList, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewHistoryResponse,
|
||||
StatsInputStatusReviewItem,
|
||||
} from "@/entities/statistics/model/statistics-input-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
StatsInputStatusApproveItem,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { isGugunOkForToolbar } from "@/entities/statistics";
|
||||
import {
|
||||
Button,
|
||||
DataTable,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
LoadingWithAnimation,
|
||||
DataTable
|
||||
} from "@common/components";
|
||||
LoadingWithAnimation} from "@common/components";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { StatsInputStatusApproveItem } from "@/entities/statistics/model/statistics-interface";
|
||||
import { StatsInputStatusInfoList } from "@/components/util/column/review/StatsInputStatusRvwColumn";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type {
|
||||
ReviewHistoryResponse,
|
||||
StatsInputStatusReviewItem,
|
||||
} from "@/entities/statistics/model/statistics-input-review-interface";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { StatsInputStatusInfoList } from "@/components/util/column/review/StatsInputStatusRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
type ReviewHistoryRow = ReviewHistoryResponse["data"][number];
|
||||
|
||||
const checkPrvnStatusCd = (code: string) => {
|
||||
@@ -54,7 +60,9 @@ const mapSewerageListToGridRows = (rawList: StatsInputStatusReviewItem[]) =>
|
||||
|
||||
// 하수도 통계 검토 00. 통계 입력 현황
|
||||
const StatsInputStatusRvw = () => {
|
||||
const { sidoList } = useSearchConditionSidoList();
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const { confirm } = useConfirm();
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [historyRowData, setHistoryRowData] = useState<ReviewHistoryRow[]>([]);
|
||||
@@ -63,6 +71,7 @@ const StatsInputStatusRvw = () => {
|
||||
const [searchedYear, setSearchedYear] = useState<string>("");
|
||||
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
|
||||
const [hasCompletedSearch, setHasCompletedSearch] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [reviewHistoryOpen, setReviewHistoryOpen] = useState<boolean>(false);
|
||||
const [historySearchInfo, setHistorySearchInfo] = useState<{
|
||||
ctpvNm: string;
|
||||
@@ -112,6 +121,7 @@ const StatsInputStatusRvw = () => {
|
||||
setLastSearchedValues(null);
|
||||
setHasCompletedSearch(false);
|
||||
setIsApproveLoading(false);
|
||||
setSubmitDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
@@ -174,8 +184,8 @@ const StatsInputStatusRvw = () => {
|
||||
setIsApproveLoading(true);
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const mdfrId = (userInfo.userUnqId ?? "").trim();
|
||||
if (!mdfrId) {
|
||||
const mdfrUuid = (userInfo.userUnqId ?? "").trim();
|
||||
if (!mdfrUuid) {
|
||||
alert("수정자 ID를 확인할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
@@ -187,7 +197,7 @@ const StatsInputStatusRvw = () => {
|
||||
.map((row) => {
|
||||
const statsCd = String(row.statsCd ?? "").trim();
|
||||
const sewerBzmnCd = String(row.sewerBzmnCd ?? sewerBzmnCdDefault).trim();
|
||||
return { statsYr, sewerBzmnCd, statsCd, mdfrId };
|
||||
return { statsYr, sewerBzmnCd, statsCd, mdfrUuid };
|
||||
})
|
||||
.filter((item) => item.statsCd.length > 0);
|
||||
|
||||
@@ -223,9 +233,7 @@ const StatsInputStatusRvw = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || "")
|
||||
? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== "ALL")
|
||||
: true;
|
||||
const gugunOkOrNotDoList = isGugunOkForToolbar(sidoList, lastSearchedValues);
|
||||
|
||||
const allSubmitStatusApproved = useMemo(
|
||||
() =>
|
||||
@@ -242,13 +250,74 @@ const StatsInputStatusRvw = () => {
|
||||
[rowData],
|
||||
);
|
||||
|
||||
const showActionToolbarCard =
|
||||
/** 진행상태(prvn) 코드가 모두 제출완료(2) */
|
||||
const allPrvnStatusSubmitted = useMemo(
|
||||
() =>
|
||||
rowData.length > 0 &&
|
||||
rowData.every((row) => String(row.prvnStatusCd ?? "") === "2"),
|
||||
[rowData],
|
||||
);
|
||||
|
||||
const showSubmitButton =
|
||||
hasCompletedSearch &&
|
||||
rowData.length > 0 &&
|
||||
gugunOkOrNotDoList &&
|
||||
!allPrvnStatusApproved &&
|
||||
!allPrvnStatusSubmitted;
|
||||
|
||||
const showApproveButton =
|
||||
hasCompletedSearch &&
|
||||
rowData.length > 0 &&
|
||||
gugunOkOrNotDoList &&
|
||||
allSubmitStatusApproved &&
|
||||
!allPrvnStatusApproved;
|
||||
|
||||
const showActionToolbarCard = showSubmitButton || showApproveButton;
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchedValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchedValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchedValues.bsnmCd ?? "";
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [lastSearchedValues, userInfo?.instSeCd]);
|
||||
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const refreshListAfterSubmit = async () => {
|
||||
if (!lastSearchedValues) return;
|
||||
try {
|
||||
const listResponse = await statisticsManagementApi.getStatsInputStatusList({
|
||||
ctpvNm: lastSearchedValues.sido,
|
||||
sggNm: lastSearchedValues.gugun === "ALL" ? "" : lastSearchedValues.gugun,
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd,
|
||||
});
|
||||
if (listResponse.status === "success" && listResponse.data) {
|
||||
const rawList = listResponse.data.sewerageInputList ?? [];
|
||||
setRowData(toGridRows(mapSewerageListToGridRows(rawList)));
|
||||
}
|
||||
} catch {
|
||||
/* 목록 갱신 실패는 별도 안내 없이 생략 */
|
||||
}
|
||||
};
|
||||
|
||||
const openReviewHistoryForRow = useCallback(
|
||||
async (row: Record<string, string | number>) => {
|
||||
if (!searchedYear) {
|
||||
@@ -297,7 +366,10 @@ const StatsInputStatusRvw = () => {
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<ApproveButton approveFunc={approveFunc} />
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={submitFunc} text="일괄 제출" />
|
||||
)}
|
||||
{showApproveButton && <ApproveButton approveFunc={approveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -357,6 +429,13 @@ const StatsInputStatusRvw = () => {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
hideOpinionFields
|
||||
onSubmitted={() => void refreshListAfterSubmit()}
|
||||
/>
|
||||
<Dialog open={reviewHistoryOpen} onOpenChange={setReviewHistoryOpen}>
|
||||
<DialogContent
|
||||
className="flex max-h-[85vh] max-w-[48rem] flex-col gap-4"
|
||||
|
||||
@@ -3,36 +3,36 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import {
|
||||
extractWaterReuseReviewList,
|
||||
extractWaterReuseReviewStatsStatusInfo,
|
||||
mapWaterReuseReviewRows,
|
||||
type WaterReuseReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-water-reuse-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { WaterReuseInfoListRvw } from "@/components/util/column/review/WaterReuseRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = WaterReuseInfoListRvw[0]?.tabKey ?? "ST00100001";
|
||||
|
||||
@@ -108,7 +108,7 @@ const WaterReuseRvw = () => {
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
@@ -133,11 +133,11 @@ const WaterReuseRvw = () => {
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
statsStatusInfo.lclgvSbmsnSttsCd != null
|
||||
? String(statsStatusInfo.lclgvSbmsnSttsCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
@@ -291,7 +291,7 @@ const WaterReuseRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
splmntRsltCn: row?.splmntRsltCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
@@ -314,7 +314,7 @@ const WaterReuseRvw = () => {
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
splmntRsltCn: param.splmntRsltCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 분리된 분뇨처리시설용 컬럼 구성 임포트
|
||||
import { ExcretaTreatmentStatusColumnConfig } from "@/components/util/column/total/SewerExcretaTreatmentStatusColumn";
|
||||
|
||||
// 집계표(TOTAL) API 및 인터페이스 명세 호출
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
ExcretaTreatmentMaintainTotalRequest
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-excreta-treatment-total-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
// 분리된 분뇨처리시설용 컬럼 구성 임포트
|
||||
import { ExcretaTreatmentStatusColumnConfig } from "@/components/util/column/total/SewerExcretaTreatmentStatusColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const ExcretaTreatmentFacilityTot = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
@@ -84,7 +83,7 @@ const ExcretaTreatmentFacilityTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={config.tabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -105,6 +104,7 @@ const ExcretaTreatmentFacilityTot = () => {
|
||||
setRowData={setRowData} // 필수 프로퍼티 에러 해결
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={config.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 분리된 개별 3개 컬럼 스펙 파일 로드
|
||||
import { PrivateSewageTab1ColumnConfig } from "@/components/util/column/total/sewerPrivateSewageColumn/SewerPrivateSewageTab1Column";
|
||||
import { PrivateSewageTab2ColumnConfig } from "@/components/util/column/total/sewerPrivateSewageColumn/SewerPrivateSewageTab2Column";
|
||||
import { PrivateSewageTab3ColumnConfig } from "@/components/util/column/total/sewerPrivateSewageColumn/SewerPrivateSewageTab3Column";
|
||||
|
||||
// 통계 API 서비스 및 통합 DTO 명세 임포트
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
PrivateSewageFacilitySummaryRequest
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-private-sewage-total-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
// 분리된 개별 3개 컬럼 스펙 파일 로드
|
||||
import { PrivateSewageTab1ColumnConfig } from "@/components/util/column/total/sewerPrivateSewageColumn/SewerPrivateSewageTab1Column";
|
||||
import { PrivateSewageTab2ColumnConfig } from "@/components/util/column/total/sewerPrivateSewageColumn/SewerPrivateSewageTab2Column";
|
||||
import { PrivateSewageTab3ColumnConfig } from "@/components/util/column/total/sewerPrivateSewageColumn/SewerPrivateSewageTab3Column";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const PrivateSewageColumnListTot = [
|
||||
PrivateSewageTab1ColumnConfig,
|
||||
PrivateSewageTab2ColumnConfig,
|
||||
@@ -132,7 +131,7 @@ const PrivateSewageFacilityTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={firstTabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{/* 하수관로 규칙과 100% 싱크 맞춘 중앙 정렬 소형 팝업 로딩 바 고정 */}
|
||||
{isLoading && (
|
||||
@@ -184,6 +183,7 @@ const PrivateSewageFacilityTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { PublicSewageFacilityColumn } from "@/components/util/column/total/PublicSewageFacilityColumn";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
const PublicSewageFacilityTot = () => {
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
@@ -52,7 +53,7 @@ const PublicSewageFacilityTot = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm isDetail={true} capacityGbn={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" />
|
||||
<SearchForm tabKey="ST00060001" isDetail={true} capacityGbn={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" defaultRegionAll />
|
||||
{isLoading && (
|
||||
<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]">
|
||||
@@ -65,6 +66,7 @@ const PublicSewageFacilityTot = () => {
|
||||
columnInfo={PublicSewageFacilityColumn}
|
||||
rowStartNum={2}
|
||||
isExcelImport={false}
|
||||
excelExportApplyStyles
|
||||
fixedColumns={2}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 분리된 중계펌프장용 컬럼 구성 임포트
|
||||
import { MediationPumpStatusColumnConfig } from "@/components/util/column/total/SewerMediationPumpStatusColumn";
|
||||
|
||||
// 집계표(TOTAL) API 및 인터페이스 명세 호출
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
MediationPumpMaintainTotalRequest
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-mediation-pump-total-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
// 분리된 중계펌프장용 컬럼 구성 임포트
|
||||
import { MediationPumpStatusColumnConfig } from "@/components/util/column/total/SewerMediationPumpStatusColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const PumpingStationTot = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
@@ -84,7 +83,7 @@ const PumpingStationTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={config.tabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -105,6 +104,7 @@ const PumpingStationTot = () => {
|
||||
setRowData={setRowData}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={config.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
// 12. 하수 및 분뇨 관리 산업 탭 설정 (경로 확인 필수)
|
||||
import { PublicSewageStaffTab1ColumnConfig } from "@/components/util/column/total/sewageExcretaIndustry/PublicSewageStaffTab1Column";
|
||||
import { PublicHumanWasteStaffTab2ColumnConfig } from "@/components/util/column/total/sewageExcretaIndustry/PublicHumanWasteStaffTab2Column";
|
||||
import { HumanWasteCollectionTab3ColumnConfig } from "@/components/util/column/total/sewageExcretaIndustry/HumanWasteCollectionTab3Column";
|
||||
import { IndividualSewageTab4ColumnConfig } from "@/components/util/column/total/sewageExcretaIndustry/IndividualSewageTab4Column";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
SewerageIndustry,
|
||||
PublicSewageStaffTab1Response,
|
||||
PublicHumanWasteStaffTab2Response,
|
||||
HumanWasteCollectionTab3Response,
|
||||
IndividualSewageTab4Response
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-sewerage-industry-interface";
|
||||
IndividualSewageTab4Response,
|
||||
PublicHumanWasteStaffTab2Response,
|
||||
PublicSewageStaffTab1Response,
|
||||
SewerageIndustry} from "@/entities/statistics/model/statistics-total/statistics-sewerage-industry-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { HumanWasteCollectionTab3ColumnConfig } from "@/components/util/column/total/sewageExcretaIndustry/HumanWasteCollectionTab3Column";
|
||||
import { IndividualSewageTab4ColumnConfig } from "@/components/util/column/total/sewageExcretaIndustry/IndividualSewageTab4Column";
|
||||
import { PublicHumanWasteStaffTab2ColumnConfig } from "@/components/util/column/total/sewageExcretaIndustry/PublicHumanWasteStaffTab2Column";
|
||||
// 12. 하수 및 분뇨 관리 산업 탭 설정 (경로 확인 필수)
|
||||
import { PublicSewageStaffTab1ColumnConfig } from "@/components/util/column/total/sewageExcretaIndustry/PublicSewageStaffTab1Column";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const SewageExcretaIndustryColumnListTot = [PublicSewageStaffTab1ColumnConfig,PublicHumanWasteStaffTab2ColumnConfig,HumanWasteCollectionTab3ColumnConfig,IndividualSewageTab4ColumnConfig].filter(Boolean);
|
||||
const firstTabKey = SewageExcretaIndustryColumnListTot[0]?.tabKey ?? "ST00120001";
|
||||
@@ -102,7 +103,7 @@ const SewageExcretaRelatedTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={firstTabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -140,6 +141,7 @@ const SewageExcretaRelatedTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 4개 탭 컬럼 설정 임포트
|
||||
import { SludgeProcessingTab1ColumnConfig } from "@/components/util/column/total/sludgeProcessing/SludgeProcessingTab1Column";
|
||||
import { DigestionTankTab2ColumnConfig } from "@/components/util/column/total/sludgeProcessing/DigestionTankTab2Column";
|
||||
import { SewageSludgeOperTab3ColumnConfig } from "@/components/util/column/total/sludgeProcessing/SewageSludgeOperTab3Column";
|
||||
import { HumanWasteSludgeTab4ColumnConfig } from "@/components/util/column/total/sludgeProcessing/HumanWasteSludgeTab4Column";
|
||||
|
||||
// API 인스턴스 및 인터페이스 임포트 (타입 안전성 확보)
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
SludgeSummaryRequest,
|
||||
SludgeProcessingTab1Response,
|
||||
DigestionTankOperTab2Response,
|
||||
HumanWasteSludgeTab4Response,
|
||||
SewageSludgeOperTab3Response,
|
||||
HumanWasteSludgeTab4Response
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-sludge-total-interface";
|
||||
SludgeProcessingTab1Response,
|
||||
SludgeSummaryRequest} from "@/entities/statistics/model/statistics-total/statistics-sludge-total-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { DigestionTankTab2ColumnConfig } from "@/components/util/column/total/sludgeProcessing/DigestionTankTab2Column";
|
||||
import { HumanWasteSludgeTab4ColumnConfig } from "@/components/util/column/total/sludgeProcessing/HumanWasteSludgeTab4Column";
|
||||
import { SewageSludgeOperTab3ColumnConfig } from "@/components/util/column/total/sludgeProcessing/SewageSludgeOperTab3Column";
|
||||
// 4개 탭 컬럼 설정 임포트
|
||||
import { SludgeProcessingTab1ColumnConfig } from "@/components/util/column/total/sludgeProcessing/SludgeProcessingTab1Column";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 탭 순회 설정
|
||||
const SludgeColumnListTot = [
|
||||
SludgeProcessingTab1ColumnConfig,
|
||||
@@ -146,7 +144,7 @@ const loadGridData = async (tabKey: string, values: SearchFormValues): Promise<b
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={firstTabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -188,6 +186,7 @@ const loadGridData = async (tabKey: string, values: SearchFormValues): Promise<b
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -2,15 +2,15 @@ import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type { SewagePipeMaintainTotalRequest } from "@/entities/statistics/model/statistics-total/statistics-sewage-pipe-maintain-total-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeMaintainColumnListTot } from "@/components/util/column/total/sewagePipeMaintain/sewagePipeMaintainColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import type { SewagePipeMaintainTotalRequest } from "@/entities/statistics/model/statistics-total/statistics-sewage-pipe-maintain-total-interface";
|
||||
|
||||
const firstTabKey = SewagePipeMaintainColumnListTot[0]?.tabKey ?? "ST00030001";
|
||||
|
||||
@@ -133,7 +133,7 @@ const SewagePipeMaintainTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={firstTabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
{isLoading && (
|
||||
<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]">
|
||||
@@ -186,6 +186,7 @@ const SewagePipeMaintainTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -2,16 +2,17 @@ import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type { SewagePipeTotalRequest } from "@/entities/statistics/model/statistics-total/statistics-sewage-pipe-total-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeColumnInfoListTot, getST00020003Config } from "@/components/util/column/total/SewagePipeTotColumn";
|
||||
import { getST00020003Config, SewagePipeColumnInfoListTot } from "@/components/util/column/total/SewagePipeTotColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import type { SewagePipeTotalRequest } from "@/entities/statistics/model/statistics-total/statistics-sewage-pipe-total-interface";
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
const firstTabKey = SewagePipeColumnInfoListTot[0]?.tabKey ?? "ST00020001";
|
||||
|
||||
@@ -178,7 +179,7 @@ const SewagePipeTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={firstTabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
{isLoading && (
|
||||
<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]">
|
||||
@@ -204,11 +205,10 @@ const SewagePipeTot = () => {
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
|
||||
// ★ [동적 처리] ST00020003인 경우 함수를 통해 컬럼 정보를 실시간으로 생성
|
||||
// ★ [동적 처리] ST00020003은 함수를 통해 컬럼 정보를 실시간으로 생성
|
||||
const currentYear = lastSearchedValuesRef.current?.year || new Date().getFullYear();
|
||||
const finalTabConfig = tabKey === "ST00020003"
|
||||
? getST00020003Config(currentYear)
|
||||
: info;
|
||||
const finalTabConfig =
|
||||
tabKey === "ST00020003" ? getST00020003Config(currentYear) : info;
|
||||
|
||||
if (!finalTabConfig.columns || !finalTabConfig.columnLayout) {
|
||||
return null;
|
||||
@@ -239,6 +239,7 @@ const SewagePipeTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={finalTabConfig.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 분리된 개별 컬럼 구성 임포트 경로 유지
|
||||
import { ReservoirStatusColumnConfig } from "@/components/util/column/total/sewerReservoir/SewerReservoirStatusColumn";
|
||||
import { PumpStationStatusColumnConfig } from "@/components/util/column/total/sewerReservoir/SewerPumpStationStatusColumn";
|
||||
|
||||
// 수정 적용한 API 및 인터페이스 명세 호출 유지
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
SewerReservoirMaintainTotalRequest
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-sewage-reservoir-total-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { PumpStationStatusColumnConfig } from "@/components/util/column/total/sewerReservoir/SewerPumpStationStatusColumn";
|
||||
// 분리된 개별 컬럼 구성 임포트 경로 유지
|
||||
import { ReservoirStatusColumnConfig } from "@/components/util/column/total/sewerReservoir/SewerReservoirStatusColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const SewerReservoirColumnListTot = [
|
||||
ReservoirStatusColumnConfig,
|
||||
PumpStationStatusColumnConfig
|
||||
@@ -118,7 +117,7 @@ const SewageReservoirPumpTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={firstTabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -168,6 +167,7 @@ const SewageReservoirPumpTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -199,6 +199,7 @@ const SewerageAggregateSummary = () => {
|
||||
<SearchForm
|
||||
isSummary={true}
|
||||
sidoSelectText="전체"
|
||||
defaultRegionAll
|
||||
searchButtonText="내려받기"
|
||||
onSubmit={(values) => void handleDownload(values)}
|
||||
isReset={false}
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 분리된 4개 개별 탭 컬럼 파일 바인딩
|
||||
import { SewerageFinancialTab1ColumnConfig } from "@/components/util/column/total/sewerageFinancial/SewerageFinancialTab1Column";
|
||||
import { SewerageFeeTab2ColumnConfig } from "@/components/util/column/total/sewerageFinancial/SewerageFeeTab2Column";
|
||||
import { SewerageUsageFeeTab3ColumnConfig } from "@/components/util/column/total/sewerageFinancial/SewerageUsageFeeTab3Column";
|
||||
import { SewerageOrgCauseTab4ColumnConfig } from "@/components/util/column/total/sewerageFinancial/SewerageOrgCauseTab4Column";
|
||||
|
||||
// API 인스턴스 및 인터페이스 호출
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
SewerageFinanceSummaryRequest
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-sewerage-finance-total-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewerageFeeTab2ColumnConfig } from "@/components/util/column/total/sewerageFinancial/SewerageFeeTab2Column";
|
||||
// 분리된 4개 개별 탭 컬럼 파일 바인딩
|
||||
import { SewerageFinancialTab1ColumnConfig } from "@/components/util/column/total/sewerageFinancial/SewerageFinancialTab1Column";
|
||||
import { SewerageOrgCauseTab4ColumnConfig } from "@/components/util/column/total/sewerageFinancial/SewerageOrgCauseTab4Column";
|
||||
import { SewerageUsageFeeTab3ColumnConfig } from "@/components/util/column/total/sewerageFinancial/SewerageUsageFeeTab3Column";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const SewerageFinanceColumnListTot = [
|
||||
SewerageFinancialTab1ColumnConfig,
|
||||
SewerageFeeTab2ColumnConfig,
|
||||
@@ -144,7 +143,7 @@ const SewerageFinanceTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={firstTabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -195,6 +194,7 @@ const SewerageFinanceTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SeweragePentrationRateTotColumn } from "@/components/util/column/total/SeweragePentrationRateTotColumn";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
const SeweragePenetrationRateTot = () => {
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
@@ -51,7 +52,7 @@ const SeweragePenetrationRateTot = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" />
|
||||
<SearchForm tabKey="ST00010001" isDetail={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" defaultRegionAll />
|
||||
{isLoading && <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" />
|
||||
@@ -62,6 +63,7 @@ const SeweragePenetrationRateTot = () => {
|
||||
columnInfo={SeweragePentrationRateTotColumn}
|
||||
rowStartNum={2}
|
||||
isExcelImport={false}
|
||||
excelExportApplyStyles
|
||||
fixedColumns={1}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
SewerageServiceLevelComplaintResponse,
|
||||
SewerageServiceLevelRequest,
|
||||
SewerageServiceLevelResidentFriendlyFactResponse
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-service-level-total-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
// 13. 하수도서비스수준 컬럼 설정
|
||||
import { SewageComplaintTab1ColumnConfig } from "@/components/util/column/total/sewerageServiceLevel/ComplaintTotColumn";
|
||||
import { ResidentFriendlyFacilityTab2ColumnConfig } from "@/components/util/column/total/sewerageServiceLevel/ResidentFriendlyFactTotColumn";
|
||||
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
SewerageServiceLevelRequest,
|
||||
SewerageServiceLevelComplaintResponse,
|
||||
SewerageServiceLevelResidentFriendlyFactResponse
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-service-level-total-interface";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const SewageServiceLevelColumnListTot = [
|
||||
SewageComplaintTab1ColumnConfig,
|
||||
@@ -91,7 +92,7 @@ const SewerageServiceLevelTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={firstTabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -129,6 +130,7 @@ const SewerageServiceLevelTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SearchForm from "../../module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// [폴더 정밀 정비 완료] 프로젝트 트리 구조 5개 컬럼 스펙 패키지 호출
|
||||
import { SewerageReuseTab1ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageReuseTab1Column";
|
||||
import { SewerageGrayWaterTab2ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageGrayWaterTab2Column";
|
||||
import { SewerageRainwaterTab3ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageRainwaterTab3Column";
|
||||
import { SewerageWwtRtlztnRegTab4ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageWwtRtlztnRegTab4Column";
|
||||
import { SewerageWwtRtlztnSncnTab5ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageWwtRtlztnSncnTab5Column";
|
||||
|
||||
// 집계표(TOTAL) API 서비스 및 인터페이스 명세 임포트
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import type {
|
||||
WaterReuseSummaryRequest
|
||||
} from "@/entities/statistics/model/statistics-total/statistics-sewerage-reuse-total-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SearchForm from "../../module/SearchForm";
|
||||
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewerageGrayWaterTab2ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageGrayWaterTab2Column";
|
||||
import { SewerageRainwaterTab3ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageRainwaterTab3Column";
|
||||
// [폴더 정밀 정비 완료] 프로젝트 트리 구조 5개 컬럼 스펙 패키지 호출
|
||||
import { SewerageReuseTab1ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageReuseTab1Column";
|
||||
import { SewerageWwtRtlztnRegTab4ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageWwtRtlztnRegTab4Column";
|
||||
import { SewerageWwtRtlztnSncnTab5ColumnConfig } from "@/components/util/column/total/sewerageReuse/SewerageWwtRtlztnSncnTab5Column";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 5개 멀티 탭 매핑을 위한 설정 취합 배열
|
||||
const WaterReuseColumnListTot = [
|
||||
SewerageReuseTab1ColumnConfig,
|
||||
@@ -157,7 +157,7 @@ const WaterReuseTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm tabKey={firstTabKey} isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{/* 정중앙 5rem 크기 고정 격리형 로딩 애니메이션 */}
|
||||
{isLoading && (
|
||||
@@ -207,6 +207,7 @@ const WaterReuseTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -12,11 +12,9 @@ import {
|
||||
DialogTitle,
|
||||
Input,
|
||||
Label,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
export interface ApprovalFormData {
|
||||
rjctOpnnCn: string;
|
||||
lastCfmtnPicNm: string;
|
||||
lastCfmtnPicTelNo: string;
|
||||
lastCfmtnPicDeptNm: string;
|
||||
@@ -34,7 +32,6 @@ export function ApprovalInputDialog({
|
||||
onOpenChange,
|
||||
onConfirm,
|
||||
}: ApprovalInputDialogProps) {
|
||||
const [rjctOpnnCn, setRjctOpnnCn] = useState("");
|
||||
const [lastCfmtnPicNm, setLastCfmtnPicNm] = useState("");
|
||||
const [lastCfmtnPicTelNo, setLastCfmtnPicTelNo] = useState("");
|
||||
const [lastCfmtnPicDeptNm, setLastCfmtnPicDeptNm] = useState("");
|
||||
@@ -43,7 +40,6 @@ export function ApprovalInputDialog({
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const resetForm = () => {
|
||||
setRjctOpnnCn("");
|
||||
setLastCfmtnPicNm("");
|
||||
setLastCfmtnPicTelNo("");
|
||||
setLastCfmtnPicDeptNm("");
|
||||
@@ -65,7 +61,6 @@ export function ApprovalInputDialog({
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const formData: ApprovalFormData = {
|
||||
rjctOpnnCn: rjctOpnnCn.trim(),
|
||||
lastCfmtnPicNm: lastCfmtnPicNm.trim(),
|
||||
lastCfmtnPicTelNo: lastCfmtnPicTelNo.trim(),
|
||||
lastCfmtnPicDeptNm: lastCfmtnPicDeptNm.trim(),
|
||||
|
||||
@@ -3,12 +3,18 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { NoData } from "@/shared/ui/no-data";
|
||||
import { CodeSelectWithAll } from "@/entities/code";
|
||||
import { generalverificationApiService } from "@/entities/statistics/api/general-verification-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import type { GeneralverificationList } from "@/entities/statistics/model/general-verification-interface";
|
||||
import {
|
||||
getInputPeriodVerificationManageBlockedMessage,
|
||||
isInputPeriodCurrent,
|
||||
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
||||
import {
|
||||
Button,
|
||||
DataTable,
|
||||
@@ -43,20 +49,31 @@ const GeneralVerificationManagement = () => {
|
||||
{},
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isExporting, setIsExporting] = useState<boolean>(false);
|
||||
const [selectedVrfcRow, setSelectedVrfcRow] = useState<number | null>(null);
|
||||
const [showRegistForm, setShowRegistForm] = useState<boolean>(false);
|
||||
const [inputPeriodBlocked, setInputPeriodBlocked] = useState(false);
|
||||
const [inputPeriodBlockedMessage, setInputPeriodBlockedMessage] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
const handleSearch = async () => {
|
||||
const handleSearch = async (
|
||||
values: {
|
||||
statsCd: string;
|
||||
useYn: string;
|
||||
vrfcClsfCd: string;
|
||||
} = searchValues,
|
||||
) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const params = Object.fromEntries(
|
||||
Object.entries({
|
||||
trgtStatsCd: searchValues.statsCd,
|
||||
vrfcClsfCd: searchValues.vrfcClsfCd,
|
||||
useYn: searchValues.useYn,
|
||||
trgtStatsCd: values.statsCd,
|
||||
vrfcClsfCd: values.vrfcClsfCd,
|
||||
useYn: values.useYn,
|
||||
}).filter(([, value]) => value !== "" && value !== "ALL"),
|
||||
);
|
||||
|
||||
@@ -83,8 +100,18 @@ const GeneralVerificationManagement = () => {
|
||||
};
|
||||
|
||||
const columns = useMemo(
|
||||
() => getGeneralVerificationColumns(handleSearch),
|
||||
[searchValues.statsCd, searchValues.vrfcClsfCd, searchValues.useYn],
|
||||
() =>
|
||||
getGeneralVerificationColumns(() => handleSearch(), {
|
||||
mutationBlocked: inputPeriodBlocked,
|
||||
blockedMessage: inputPeriodBlockedMessage,
|
||||
}),
|
||||
[
|
||||
searchValues.statsCd,
|
||||
searchValues.vrfcClsfCd,
|
||||
searchValues.useYn,
|
||||
inputPeriodBlocked,
|
||||
inputPeriodBlockedMessage,
|
||||
],
|
||||
);
|
||||
|
||||
const table = useReactTable({
|
||||
@@ -102,15 +129,76 @@ const GeneralVerificationManagement = () => {
|
||||
setShowRegistForm(false);
|
||||
};
|
||||
|
||||
const handleExportExcel = async () => {
|
||||
try {
|
||||
setIsExporting(true);
|
||||
const response = await generalverificationApiService.getVerifyExcelList();
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "엑셀 데이터 조회 중 오류가 발생했습니다.");
|
||||
return;
|
||||
}
|
||||
const list = response.data ?? [];
|
||||
if (list.length === 0) {
|
||||
alert("내보낼 데이터가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const header = [
|
||||
"지표명",
|
||||
"검증명",
|
||||
"검증타입",
|
||||
"소수점 자리수",
|
||||
"소수점 자리 처리 방식",
|
||||
"검증식",
|
||||
];
|
||||
const rows = list.map((item) => [
|
||||
item.trgtStatsNm,
|
||||
item.vrfcNm,
|
||||
item.vrfcClsfNm,
|
||||
item.dcptDigtCnt,
|
||||
item.dcptPrcsMthNm,
|
||||
item.vrfcEqtnCn,
|
||||
]);
|
||||
const worksheet = XLSX.utils.aoa_to_sheet([header, ...rows]);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "일반검증목록");
|
||||
XLSX.writeFile(workbook, "일반검증목록.xlsx");
|
||||
} catch {
|
||||
alert("엑셀 내보내기 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
const resetValues = { statsCd: "", useYn: "", vrfcClsfCd: "" };
|
||||
setSearchValues(resetValues);
|
||||
setRowSelection({});
|
||||
setSelectedVrfcRow(null);
|
||||
handleSearch();
|
||||
setShowRegistForm(false);
|
||||
void handleSearch(resetValues);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleSearch();
|
||||
void handleSearch();
|
||||
void (async () => {
|
||||
try {
|
||||
const statsYr = String(new Date().getFullYear() - 1);
|
||||
const response =
|
||||
await statisticsInputSewSupplyRateApi.checkInputPeriod(statsYr);
|
||||
if (response.status !== "success") return;
|
||||
const blocked = isInputPeriodCurrent(response.data);
|
||||
setInputPeriodBlocked(blocked);
|
||||
setInputPeriodBlockedMessage(
|
||||
blocked
|
||||
? getInputPeriodVerificationManageBlockedMessage(response.data)
|
||||
: null,
|
||||
);
|
||||
} catch {
|
||||
setInputPeriodBlocked(false);
|
||||
setInputPeriodBlockedMessage(null);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -148,11 +236,11 @@ const GeneralVerificationManagement = () => {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ALL">전체</SelectItem>
|
||||
<SelectItem value="DO_SI">
|
||||
도단위 검증
|
||||
<SelectItem value="TOTAL">
|
||||
총계 검증
|
||||
</SelectItem>
|
||||
<SelectItem value="SI_GU">
|
||||
시단위 검증
|
||||
<SelectItem value="ROW">
|
||||
행 검증
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -182,7 +270,7 @@ const GeneralVerificationManagement = () => {
|
||||
<Button
|
||||
color="green"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleSearch}
|
||||
onClick={() => void handleSearch()}
|
||||
>
|
||||
조회
|
||||
</Button>
|
||||
@@ -202,16 +290,39 @@ const GeneralVerificationManagement = () => {
|
||||
<h3 className="text-2xl font-medium text-label">
|
||||
일반 검증 목록
|
||||
</h3>
|
||||
<RegistDialog
|
||||
open={showRegistForm}
|
||||
onOpenChange={setShowRegistForm}
|
||||
onSuccess={handleSearch}
|
||||
>
|
||||
<Button>등록</Button>
|
||||
</RegistDialog>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
className="h-[3.2rem] w-[96px] bg-gradient-green px-6 hover:bg-gradient-green-hover"
|
||||
type="button"
|
||||
disabled={isExporting}
|
||||
onClick={() => void handleExportExcel()}
|
||||
>
|
||||
<i className="diveicon di-file-excel" />
|
||||
<span>내보내기</span>
|
||||
</Button>
|
||||
{inputPeriodBlocked ? (
|
||||
<span
|
||||
title={
|
||||
inputPeriodBlockedMessage ?? undefined
|
||||
}
|
||||
>
|
||||
<Button disabled type="button">
|
||||
등록
|
||||
</Button>
|
||||
</span>
|
||||
) : (
|
||||
<RegistDialog
|
||||
open={showRegistForm}
|
||||
onOpenChange={setShowRegistForm}
|
||||
onSuccess={() => void handleSearch()}
|
||||
>
|
||||
<Button>등록</Button>
|
||||
</RegistDialog>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="card !mx-2 !my-4 h-full overflow-auto !p-0">
|
||||
{isLoading && (
|
||||
{(isLoading || isExporting) && (
|
||||
<div className="[data-theme='black']:bg-transparent absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
@@ -232,7 +343,11 @@ const GeneralVerificationManagement = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<DetailComponent selectedVrfcRow={selectedVrfcRow} />
|
||||
<DetailComponent
|
||||
selectedVrfcRow={selectedVrfcRow}
|
||||
mutationBlocked={inputPeriodBlocked}
|
||||
blockedMessage={inputPeriodBlockedMessage}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
@@ -23,9 +24,9 @@ import {
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useUserStore } from "@/shared/lib";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import {
|
||||
type AdstZoneResponseDto,
|
||||
IMR_DETAIL_VISIBLE_COLUMNS_BY_STATS_CD,
|
||||
IMR_DETAIL_VISIBLE_COLUMNS_DEFAULT,
|
||||
imrApi,
|
||||
@@ -36,8 +37,8 @@ import {
|
||||
stAnlsRsltApi,
|
||||
type StAnlsRsltResponse,
|
||||
statisticsAnalysisMasterService,
|
||||
useStAnlsSidoCombo,
|
||||
useStAnlsSigunguCombo,
|
||||
useSggList,
|
||||
useSidoList,
|
||||
} from "@/entities/statistics";
|
||||
import {
|
||||
Button,
|
||||
@@ -54,6 +55,7 @@ import {
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
@@ -71,7 +73,7 @@ type DateRangeValue = {
|
||||
};
|
||||
|
||||
const EMPTY_DATA: never[] = [];
|
||||
const EMPTY_ADST_LIST: AdstZoneResponseDto[] = [];
|
||||
const REGION_ALL_VALUE = "ALL";
|
||||
|
||||
function getImrDetailGridColumnInfo(statsCd: string): ColumnInfo {
|
||||
const visible =
|
||||
@@ -96,6 +98,21 @@ function getVerificationYearOptions(): string[] {
|
||||
);
|
||||
}
|
||||
|
||||
function applySingleOptionRegionDefault<T>(
|
||||
list: T[] | undefined,
|
||||
setValue: Dispatch<SetStateAction<string>>,
|
||||
resolveValue: (item: T) => string,
|
||||
) {
|
||||
if (!list || list.length !== 1) return;
|
||||
|
||||
const onlyItem = list[0];
|
||||
if (!onlyItem) return;
|
||||
|
||||
setValue((prev) =>
|
||||
prev === REGION_ALL_VALUE ? resolveValue(onlyItem) : prev,
|
||||
);
|
||||
}
|
||||
|
||||
const ImrVerificationHistory = () => {
|
||||
const yearOptions = useMemo(() => getVerificationYearOptions(), []);
|
||||
const defaultYear: string =
|
||||
@@ -104,8 +121,9 @@ const ImrVerificationHistory = () => {
|
||||
: "";
|
||||
|
||||
// Area A Search States
|
||||
const [searchSido, setSearchSido] = useState<string>("");
|
||||
const [searchSigungu, setSearchSigungu] = useState<string>("");
|
||||
const [searchSido, setSearchSido] = useState<string>(REGION_ALL_VALUE);
|
||||
const [searchSigungu, setSearchSigungu] =
|
||||
useState<string>(REGION_ALL_VALUE);
|
||||
const [searchStatsYr, setSearchStatsYr] = useState<string>(defaultYear);
|
||||
const [searchStatsCd, setSearchStatsCd] = useState<string>("");
|
||||
const [dateRange, setDateRange] = useState<DateRangeValue | undefined>({
|
||||
@@ -139,10 +157,39 @@ const ImrVerificationHistory = () => {
|
||||
pageSize: 5,
|
||||
});
|
||||
|
||||
const { data: sidoData } = useStAnlsSidoCombo();
|
||||
const { data: sigunguData } = useStAnlsSigunguCombo(searchSido);
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const sigunguOptions = sigunguData ?? EMPTY_ADST_LIST;
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const userInstSeCd = userInfo?.instSeCd;
|
||||
const { data: sidoData } = useSidoList({ instSeCd: userInstSeCd });
|
||||
const sidoOptions = sidoData?.sidoList ?? [];
|
||||
const selectedSidoBsnmCd =
|
||||
sidoOptions.find((item) => item.sido === searchSido)?.bsnmCd ?? "";
|
||||
const { data: sigunguData } = useSggList(searchSido, {
|
||||
instSeCd: userInstSeCd,
|
||||
gugunSkipYn: doList.includes(selectedSidoBsnmCd) ? "N" : "Y",
|
||||
});
|
||||
const sigunguOptions = sigunguData ?? [];
|
||||
const allowRegionAutoDefaultRef = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!allowRegionAutoDefaultRef.current) return;
|
||||
|
||||
applySingleOptionRegionDefault(
|
||||
sidoData?.sidoList,
|
||||
setSearchSido,
|
||||
(item) => item.sido,
|
||||
);
|
||||
}, [sidoData?.sidoList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!allowRegionAutoDefaultRef.current) return;
|
||||
if (searchSido === REGION_ALL_VALUE) return;
|
||||
|
||||
applySingleOptionRegionDefault(
|
||||
sigunguData,
|
||||
setSearchSigungu,
|
||||
(item) => item.gugun,
|
||||
);
|
||||
}, [sigunguData, searchSido]);
|
||||
|
||||
const imrDetailColumnInfo = useMemo(
|
||||
() => getImrDetailGridColumnInfo(selectedHistory?.statsCd ?? ""),
|
||||
@@ -241,9 +288,15 @@ const ImrVerificationHistory = () => {
|
||||
vrfcMthdCd: "IMR",
|
||||
sort: "anlsSn,desc",
|
||||
};
|
||||
if (searchSido !== "")
|
||||
if (searchSido !== "" && searchSido !== REGION_ALL_VALUE) {
|
||||
const selectedSggBsnmCd = sigunguOptions.find(
|
||||
(item) => item.gugun === searchSigungu,
|
||||
)?.bsnmCd;
|
||||
params.sewerBzmnCd =
|
||||
searchSigungu !== "" ? searchSigungu : searchSido;
|
||||
searchSigungu !== "" && searchSigungu !== REGION_ALL_VALUE
|
||||
? (selectedSggBsnmCd ?? "")
|
||||
: selectedSidoBsnmCd;
|
||||
}
|
||||
if (searchStatsYr) params.statsYr = searchStatsYr;
|
||||
if (searchStatsCd) params.statsCd = searchStatsCd;
|
||||
if (dateRange?.from)
|
||||
@@ -258,8 +311,9 @@ const ImrVerificationHistory = () => {
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchSido("");
|
||||
setSearchSigungu("");
|
||||
allowRegionAutoDefaultRef.current = false;
|
||||
setSearchSido(REGION_ALL_VALUE);
|
||||
setSearchSigungu(REGION_ALL_VALUE);
|
||||
setSearchStatsCd("");
|
||||
const defaultRange = {
|
||||
from: dayjs().subtract(1, "month").toDate(),
|
||||
@@ -397,19 +451,22 @@ const ImrVerificationHistory = () => {
|
||||
value={searchSido}
|
||||
onValueChange={(val) => {
|
||||
setSearchSido(val);
|
||||
setSearchSigungu("");
|
||||
setSearchSigungu(REGION_ALL_VALUE);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[14rem]">
|
||||
<SelectValue placeholder="시도 선택" />
|
||||
<SelectValue placeholder="전체" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={REGION_ALL_VALUE}>
|
||||
전체
|
||||
</SelectItem>
|
||||
{sidoOptions.map((opt) => (
|
||||
<SelectItem
|
||||
key={opt.adstCd}
|
||||
value={opt.adstCd}
|
||||
key={opt.adstDstrCd}
|
||||
value={opt.sido}
|
||||
>
|
||||
{opt.adstNm}
|
||||
{opt.sido}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -423,18 +480,24 @@ const ImrVerificationHistory = () => {
|
||||
<Select
|
||||
value={searchSigungu}
|
||||
onValueChange={setSearchSigungu}
|
||||
disabled={searchSido === ""}
|
||||
disabled={
|
||||
searchSido === "" ||
|
||||
searchSido === REGION_ALL_VALUE
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[14rem]">
|
||||
<SelectValue placeholder="시군구 선택" />
|
||||
<SelectValue placeholder="전체" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={REGION_ALL_VALUE}>
|
||||
전체
|
||||
</SelectItem>
|
||||
{sigunguOptions.map((opt) => (
|
||||
<SelectItem
|
||||
key={opt.adstCd}
|
||||
value={opt.adstCd}
|
||||
key={opt.adstDstrCd}
|
||||
value={opt.gugun}
|
||||
>
|
||||
{opt.adstNm}
|
||||
{opt.gugun}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
||||
@@ -14,14 +14,14 @@ import { CodeApi, CodeSelect } from "@/entities/code";
|
||||
import {
|
||||
type ApproveRequest,
|
||||
type RejectRequest,
|
||||
type ReportStatusResponseDto,
|
||||
statisticsApprovalApi,
|
||||
type StatisticsVerificationFilterState,
|
||||
statisticsVerificationReportColumnInfo,
|
||||
statisticsVerificationResultColumnInfo,
|
||||
type StatisticsVerificationSubFilterState,
|
||||
type StatsInputStatusList,
|
||||
useStAnlsSidoCombo,
|
||||
useStAnlsSigunguCombo,
|
||||
useSggList,
|
||||
useSidoList,
|
||||
useStatsVrfcRptData,
|
||||
useStatsVrfcRsltData,
|
||||
type VerifyResultItem,
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
ApprovalInputDialog,
|
||||
} from "@/components/statistics/verification/ApprovalInputDialog";
|
||||
import type { CellEditedCallbackParams } from "@/components/type/DataGrid.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const getYearOptions = () => {
|
||||
@@ -71,8 +72,8 @@ const getYearOptions = () => {
|
||||
};
|
||||
|
||||
function isSameStatsInputStatusRow(
|
||||
a: StatsInputStatusList,
|
||||
b: StatsInputStatusList,
|
||||
a: ReportStatusResponseDto,
|
||||
b: ReportStatusResponseDto,
|
||||
): boolean {
|
||||
return (
|
||||
a.ctpvNm === b.ctpvNm && a.sggNm === b.sggNm && a.statsCd === b.statsCd
|
||||
@@ -125,17 +126,22 @@ function useGridCheckboxSelection<T>(
|
||||
return { handleItemChecked, handleAllChecked };
|
||||
}
|
||||
|
||||
const REGION_ALL_OPTION = { label: "전체", value: "ALL" };
|
||||
|
||||
const DEFAULT_FILTERS: StatisticsVerificationFilterState = {
|
||||
ctpvNm: null,
|
||||
sggNm: null,
|
||||
statsYr: String(dayjs().year() - 1),
|
||||
sewerBzmnCd: null,
|
||||
statsCd: null,
|
||||
laeSbmsnSttsCd: "3",
|
||||
pbcpSbmsnSttsCd: null,
|
||||
ctpvSewerBzmnCd: "ALL",
|
||||
sggSewerBzmnCd: "ALL",
|
||||
};
|
||||
|
||||
const DEFAULT_SUB_FILTERS: StatisticsVerificationSubFilterState = {
|
||||
...DEFAULT_FILTERS,
|
||||
laeSbmsnSttsCd: "3",
|
||||
prcsYnList: "1,2",
|
||||
rejectCode: "0",
|
||||
pbcpAprvYn: "N",
|
||||
@@ -152,6 +158,13 @@ const REJECT_STATUS_OPTIONS = [
|
||||
{ label: "공단반려", value: "2" },
|
||||
];
|
||||
|
||||
const PBCP_SBMSN_STTS_OPTIONS = [
|
||||
{ label: "미작성", value: "0" },
|
||||
{ label: "작성중", value: "1" },
|
||||
{ label: "승인", value: "3" },
|
||||
{ label: "반려", value: "4" },
|
||||
];
|
||||
|
||||
function StatisticsVerificationReport() {
|
||||
const queryClient = useQueryClient();
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
@@ -162,7 +175,7 @@ function StatisticsVerificationReport() {
|
||||
const [subFilters, setSubFilters] =
|
||||
useState<StatisticsVerificationSubFilterState>(DEFAULT_SUB_FILTERS);
|
||||
const [selectedReportRows, setSelectedReportRows] = useState<
|
||||
StatsInputStatusList[]
|
||||
ReportStatusResponseDto[]
|
||||
>([]);
|
||||
const [selectedResultRows, setSelectedResultRows] = useState<
|
||||
VerifyResultItem[]
|
||||
@@ -175,10 +188,15 @@ function StatisticsVerificationReport() {
|
||||
const prevLengthRef = useRef(0);
|
||||
const rejectedVrfcSnRef = useRef<Set<number>>(new Set());
|
||||
|
||||
const { data: sidoData } = useStAnlsSidoCombo();
|
||||
const { data: sigunguData } = useStAnlsSigunguCombo(
|
||||
filters.sewerBzmnCd ?? "",
|
||||
);
|
||||
const userInstSeCd = userInfo?.instSeCd;
|
||||
const { data: sidoData } = useSidoList({ instSeCd: userInstSeCd });
|
||||
const sidoList = sidoData?.sidoList ?? [];
|
||||
const selectedSidoBsnmCd = filters.ctpvSewerBzmnCd ?? "";
|
||||
const { data: sigunguData } = useSggList(filters.ctpvNm ?? "", {
|
||||
instSeCd: userInstSeCd,
|
||||
gugunSkipYn: doList.includes(selectedSidoBsnmCd) ? "N" : "Y",
|
||||
});
|
||||
|
||||
const detailParams = useMemo(
|
||||
() => ({
|
||||
statsYr: filters.statsYr,
|
||||
@@ -221,9 +239,6 @@ function StatisticsVerificationReport() {
|
||||
const statisticsVerificationReportQuery = useStatsVrfcRptData(
|
||||
statisticsVerificationReportFilters,
|
||||
);
|
||||
const statisticsVerificationResultQuery =
|
||||
useStatsVrfcRsltData(detailParams);
|
||||
|
||||
const statisticsVerificationReportData =
|
||||
statisticsVerificationReportQuery.data ?? [];
|
||||
const statisticsVerificationReportGridRows = useMemo(
|
||||
@@ -231,6 +246,8 @@ function StatisticsVerificationReport() {
|
||||
[statisticsVerificationReportData],
|
||||
);
|
||||
|
||||
const statisticsVerificationResultQuery =
|
||||
useStatsVrfcRsltData(detailParams);
|
||||
const statisticsVerificationResultData = useMemo(
|
||||
() =>
|
||||
statisticsVerificationResultQuery.data?.pages.flatMap(
|
||||
@@ -409,14 +426,25 @@ function StatisticsVerificationReport() {
|
||||
|
||||
// 시도 변경 시
|
||||
if (key === "ctpvNm") {
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
sewerBzmnCd: value,
|
||||
ctpvNm: optionData?.label,
|
||||
ctpvSewerBzmnCd: value,
|
||||
sggNm: "",
|
||||
sggSewerBzmnCd: "",
|
||||
}));
|
||||
if (value === "ALL") {
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
ctpvNm: null,
|
||||
sggNm: null,
|
||||
sewerBzmnCd: null,
|
||||
ctpvSewerBzmnCd: "ALL",
|
||||
sggSewerBzmnCd: "ALL",
|
||||
}));
|
||||
} else {
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
sewerBzmnCd: value,
|
||||
ctpvNm: optionData?.label,
|
||||
ctpvSewerBzmnCd: value,
|
||||
sggNm: null,
|
||||
sggSewerBzmnCd: "ALL",
|
||||
}));
|
||||
}
|
||||
setSubFilters((prev) => ({
|
||||
...prev,
|
||||
ctpvNm: "",
|
||||
@@ -428,12 +456,25 @@ function StatisticsVerificationReport() {
|
||||
|
||||
// 시군구 변경 시
|
||||
if (key === "sggNm") {
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
sewerBzmnCd: value,
|
||||
sggNm: optionData?.label,
|
||||
sggSewerBzmnCd: value,
|
||||
}));
|
||||
if (value === "ALL") {
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
sggNm: null,
|
||||
sggSewerBzmnCd: "ALL",
|
||||
sewerBzmnCd:
|
||||
prev.ctpvSewerBzmnCd &&
|
||||
prev.ctpvSewerBzmnCd !== "ALL"
|
||||
? prev.ctpvSewerBzmnCd
|
||||
: null,
|
||||
}));
|
||||
} else {
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
sewerBzmnCd: value,
|
||||
sggNm: optionData?.label,
|
||||
sggSewerBzmnCd: value,
|
||||
}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -486,7 +527,7 @@ function StatisticsVerificationReport() {
|
||||
async function handleApprovalConfirm(formData: ApprovalFormData) {
|
||||
const commonParam = {
|
||||
statsYr: filters.statsYr,
|
||||
mdfrId: userInfo?.userUnqId ?? "",
|
||||
mdfrUuid: userInfo?.userUnqId ?? "",
|
||||
};
|
||||
const approvalRequests = selectedReportRows.map((row) => ({
|
||||
...commonParam,
|
||||
@@ -508,7 +549,7 @@ function StatisticsVerificationReport() {
|
||||
async function handleReject() {
|
||||
const commonParam = {
|
||||
statsYr: filters.statsYr,
|
||||
mdfrId: userInfo?.userUnqId ?? "",
|
||||
mdfrUuid: userInfo?.userUnqId ?? "",
|
||||
};
|
||||
|
||||
const requestRows = selectedResultRows.map((row) => {
|
||||
@@ -552,6 +593,18 @@ function StatisticsVerificationReport() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleReportRefresh() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.ST_SERVICE.INPUT, "validation-report"],
|
||||
});
|
||||
}
|
||||
|
||||
function handleResultRefresh() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.ST_SERVICE.INPUTVERIFY, "verify-results"],
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<ApprovalInputDialog
|
||||
@@ -563,25 +616,31 @@ function StatisticsVerificationReport() {
|
||||
<div className="flex items-center gap-6">
|
||||
<FilterSelect
|
||||
label="시도"
|
||||
value={filters.ctpvSewerBzmnCd ?? ""}
|
||||
value={filters.ctpvSewerBzmnCd ?? "ALL"}
|
||||
onChange={handleFilterChange("ctpvNm")}
|
||||
options={
|
||||
sidoData?.map((item) => ({
|
||||
label: item.adstNm,
|
||||
value: item.adstCd,
|
||||
})) ?? []
|
||||
}
|
||||
options={[
|
||||
REGION_ALL_OPTION,
|
||||
...sidoList.map((item) => ({
|
||||
label: item.sido,
|
||||
value: item.bsnmCd,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
<FilterSelect
|
||||
label="시군구"
|
||||
value={filters.sggSewerBzmnCd ?? ""}
|
||||
value={filters.sggSewerBzmnCd ?? "ALL"}
|
||||
onChange={handleFilterChange("sggNm")}
|
||||
options={
|
||||
sigunguData?.map((item) => ({
|
||||
label: item.adstNm,
|
||||
value: item.adstCd,
|
||||
})) ?? []
|
||||
disabled={
|
||||
!filters.ctpvSewerBzmnCd ||
|
||||
filters.ctpvSewerBzmnCd === "ALL"
|
||||
}
|
||||
options={[
|
||||
REGION_ALL_OPTION,
|
||||
...(sigunguData?.map((item) => ({
|
||||
label: item.gugun,
|
||||
value: item.bsnmCd,
|
||||
})) ?? []),
|
||||
]}
|
||||
/>
|
||||
<FilterSelect
|
||||
label="년도"
|
||||
@@ -589,6 +648,12 @@ function StatisticsVerificationReport() {
|
||||
onChange={handleFilterChange("statsYr")}
|
||||
options={getYearOptions()}
|
||||
/>
|
||||
<FilterSelect
|
||||
label="공단진행상태"
|
||||
value={filters.pbcpSbmsnSttsCd ?? ""}
|
||||
onChange={handleFilterChange("pbcpSbmsnSttsCd")}
|
||||
options={PBCP_SBMSN_STTS_OPTIONS}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label>통계지표</Label>
|
||||
<CodeSelect
|
||||
@@ -601,6 +666,9 @@ function StatisticsVerificationReport() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-end justify-end gap-2">
|
||||
<Button color="gray" onClick={handleReportRefresh}>
|
||||
새로고침
|
||||
</Button>
|
||||
<Button color="white" onClick={handleReset}>
|
||||
초기화
|
||||
</Button>
|
||||
@@ -626,7 +694,7 @@ function StatisticsVerificationReport() {
|
||||
setRowData={() => {}}
|
||||
showHeaderCheckbox={true}
|
||||
gridHeight={30}
|
||||
onRowClick={(row: StatsInputStatusList) => {
|
||||
onRowClick={(row: ReportStatusResponseDto) => {
|
||||
setSubFilters((prev) => ({
|
||||
...prev,
|
||||
ctpvNm: row?.ctpvNm ?? "",
|
||||
@@ -662,6 +730,9 @@ function StatisticsVerificationReport() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end justify-end gap-2">
|
||||
<Button color="gray" onClick={handleResultRefresh}>
|
||||
새로고침
|
||||
</Button>
|
||||
<Button color="white" onClick={handleSubFilterReset}>
|
||||
초기화
|
||||
</Button>
|
||||
@@ -717,6 +788,7 @@ interface FilterSelectProps {
|
||||
) => void;
|
||||
options: Array<{ label: string; value: string }>;
|
||||
value?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function FilterCombobox({
|
||||
@@ -783,12 +855,19 @@ function FilterCombobox({
|
||||
);
|
||||
}
|
||||
|
||||
function FilterSelect({ label, onChange, options, value }: FilterSelectProps) {
|
||||
function FilterSelect({
|
||||
label,
|
||||
onChange,
|
||||
options,
|
||||
value,
|
||||
disabled,
|
||||
}: FilterSelectProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="whitespace-nowrap">{label}</Label>
|
||||
<Select
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onValueChange={(value) =>
|
||||
onChange(
|
||||
value,
|
||||
|
||||
@@ -39,8 +39,8 @@ import { VerificationTargetDialog } from "./VerificationTargetDialog";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
|
||||
const TAB_CONFIG = {
|
||||
HB: { value: "HB", label: "H-B", columnInfo: hbArticleColumnInfo },
|
||||
IMR: { value: "IMR", label: "I-MR", columnInfo: imrArticleColumnInfo },
|
||||
HB: { value: "HB", label: "모집단", columnInfo: hbArticleColumnInfo },
|
||||
IMR: { value: "IMR", label: "시계열", columnInfo: imrArticleColumnInfo },
|
||||
GENERAL: {
|
||||
value: "GENERAL",
|
||||
label: "일반",
|
||||
|
||||
@@ -40,8 +40,8 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
|
||||
);
|
||||
const [checkedSns, setCheckedSns] = useState<string[]>([]);
|
||||
const [appliedFilter, setAppliedFilter] = useState<AppliedFilter>({
|
||||
sido: "",
|
||||
gugun: "",
|
||||
sido: "ALL",
|
||||
gugun: "ALL",
|
||||
status: "",
|
||||
});
|
||||
const [selectedRowId, setSelectedRowId] = useState<number | null>(null);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user