Compare commits

..
Author SHA1 Message Date
100JM 03a08a320a [ST] - fix: DB 표준화에 따른 소스 수정 2026-10-14 14:24:03 +09:00
rsone001 f2ec9327fd feat: [ST] - eval 제거 2026-09-30 17:17:01 +09:00
yangsein 5331e10c96 [refactor] 대시보드 - 통계처리현황 반려되어 제출중인 상태 구분 처리 포함 2026-09-30 16:59:05 +09:00
rsone001 de8971bea6 [ST] fix: 대시보드 통계 처리 현황 panel css 수정 2026-09-08 13:43:26 +09:00
rsone001 a7fd6a8e16 [ST] - fix: 취약점 점검 관련 조치 2026-09-07 14:26:00 +09:00
rsone001 8039ab8281 [ST] - refactor: 2-5 맨홀 지표 변경된 테이블에 맞게 수정, 일반검증 항목 입력기간일 시 등록/수정 불가 2026-09-04 16:05:26 +09:00
rsone001 794691f05e [ST] - fix: 제출결과(검증)화면에 시설명 컬럼 추가 2026-09-03 16:29:40 +09:00
rsone001 e58b84a01b [ST] - fix: input 제출 - 시계열 검증 요청 response & request 수정, 대시보드 구성 수정 2026-09-03 15:27:19 +09:00
rsone001 3d51f003c0 Merge remote-tracking branch 'origin/feature/sein' into iipbs/st/dev 2026-09-03 13:52:34 +09:00
yangsein b5a99e5f59 [feat] 대쉬 보드 일정관리 제거 , 통계 현황상태 추가 2026-09-03 13:44:09 +09:00
rsone001 afca685fd4 [ST] - fix: 2-3 매설연장 관리자일시 수정 가능하도록 적용, SearchFrom 로직 수정 2026-09-03 11:26:23 +09:00
rsone001 261941452a [ST] fix - 2-2 지표 컬럼 설정 및 저장 제출 로직 수정 2026-09-02 16:41:12 +09:00
rsone001 8576ef83b5 [ST] - refactor: 6-1 중심처리시설 row는 위탁계약기간 직접 수정 못하도록 적용 2026-09-01 16:25:55 +09:00
rsone001 e80b8953a0 [ST] - fix: 일반검증 수식 사용자 직접입력 등록시 공백제거 2026-09-01 14:41:49 +09:00
rsone001 e8b309460e [ST] input 특광역시 로직 수정, 일반검증 엑셀 다운로드 추가, 모든 number 타입 컬럼에 footer 추가 2026-08-28 17:08:20 +09:00
chanii8 37ac927c45 [st] feat: 시계열 검토 이력 > 지자체 진입 시 시도/시군구 자동 선택 2026-08-28 17:05:40 +09:00
chanii8 bf9a34cbe8 [st] feat: 하수도 통계 검증 > 시도/시군구 셀렉트에 '전체' 옵션 추가 2026-08-28 15:55:05 +09:00
rsone001 18a11ab5fa [ST] - refactor: 입력화면 전용 시도 조회 api 추가 2026-08-27 17:22:34 +09:00
rsone001 214ff1a435 [ST] - fix: 일반 검증 타입 총계 검증일 시 group by 항목 렌더링 되지 않게 수정 2026-08-27 15:44:45 +09:00
rsone001 d7cc579335 [ST] - refactor: 지난검증결과 불러오기 필드 추가(시설명) 2026-08-27 15:32:12 +09:00
rsone001 9b53c1ae08 [ST] - fix: 일반검증 항목 관리, 참조 지표(컬럼) 오류 로직 수정 2026-08-27 13:07:33 +09:00
rsone001 64ed53cf4d [ST] refactor: input - 자동 계산 cell 부동소수점 문제 해결을 위해 decimal.js 라이브러리 추가 및 계산 로직 수정, 6-2 지표 로직 수정 2026-08-25 16:04:31 +09:00
rsone001 043fd3d4a8 [ST] - 입력 6-1 공공하수처리시설 컬럼 설정 변경 2026-08-24 17:19:33 +09:00
rsone001 6dd09971f1 [ST] - 컬럼 자동 계산식 오류 수정 2026-08-24 16:39:24 +09:00
chanii8 23424b5eee [st] chore: 통계 검증 리포트 초기 조회 파라미터 수정 2026-08-24 14:09:12 +09:00
chanii8 3f190dfff6 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-21 17:27:37 +09:00
chanii8 6cc02da7a4 [st] feat: 통계 검증 리포트 목록 조회 API 수정 2026-08-21 17:27:30 +09:00
rsone001 c87ccf8788 [ST] - fix: 입력 저장&제출시 빈값 null처리 2026-08-21 14:56:34 +09:00
rsone001 8e757ab9db Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-20 16:31:13 +09:00
rsone001 de7e66afb4 [ST] - fix: 11-4 데이터 체크 수정 2026-08-20 16:31:00 +09:00
chanii8 c981b83284 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-20 13:07:50 +09:00
chanii8 927bb7cb3a [st] chore: eslint 해결 2026-08-20 11:48:18 +09:00
chanii8 725976b5c0 [st] feat: 시도/시군구 조회 쿼리 변경 2026-08-20 11:47:50 +09:00
rsone001 cdbe6a855f [ST] - fix: 검토 하수관로 컬럼 수정 및 엑셀 내보내기 셀 스타일 수정 2026-08-19 12:31:37 +09:00
chanii8 a1c96e9e98 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-19 11:58:06 +09:00
chanii8 1488805242 [as] feat: 통계 검증 리포트 새로고침 버튼 추가 2026-08-19 11:57:58 +09:00
rsone001 5b12f81b8f [ST] - fix: 통계 집계표 조회 기본값 수정, 엑셀 다운로드 스타일 적용, 요약표 마우스 툴팁 추가 / 입력단 오류 수정 및 검토단 컬럼 설정 수정 2026-08-18 17:44:22 +09:00
rsone001 df24e7c2a8 [ST] - fix: 일반 검증 수식 안내 문구 수정 2026-08-13 16:20:54 +09:00
rsone001 3b5fee047b [ST] - fix: qa 조치, 일반 검증 개발 수정 2026-08-13 15:30:00 +09:00
rsone001 181730bcde [ST] - fix: 입력: 새로고침 시 열려있는 모든 텝의 공통 api가 실행되는 현상 수정, 하수도보급률 저장&제출 request 수정(외국인총인구 제거, 총면적 추가), 검증 툴팁 max-width 추가, 지난 검증 결과 및 제출 결과 데이터 마우스 툴팁 추가 2026-08-10 17:58:05 +09:00
chanii8 678af2f06e [st] chore: 승인 정보 입력 폼 수정 2026-08-10 15:48:19 +09:00
rsone001 82d67bb4c6 [ST] - fix: 검증 항목 명칭 변경 2026-08-07 17:22:08 +09:00
rsone001 85ef1805e5 [ST] - 공단 오류 및 개선 요청사항 처리 2026-08-07 16:24:39 +09:00
chanii8 26f64988ef [st] typo: 404 페이지 문구 수정 2026-08-06 17:06:00 +09:00
chanii8 29e39f98af Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-06 15:15:09 +09:00
chanii8 5192f3e3c0 [st] feat: 404 페이지 구현 2026-08-06 15:15:04 +09:00
rsone001 83de317308 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-05 10:09:58 +09:00
rsone001 84b1fd07ae [ST] - management service code refresh 로직 추가 2026-08-05 10:08:26 +09:00
chanii8 a88f8ab33e [st] feat: 로그아웃 라우트 핸들러 적용 2026-08-03 15:56:48 +09:00
chanii8 c67b5844b6 [st] chore: metadata 수정 2026-08-03 11:14:48 +09:00
332 changed files with 12891 additions and 9232 deletions
+8 -8
View File
@@ -1,17 +1,17 @@
import "@/app/styles/base.css"; import "@/app/styles/base.css";
export const metadata = { export const metadata = {
title: "하수도플랫폼 통합관리시스템", title: "유역하수도 통합 정보플랫폼",
}; };
export default function AuthLayout({ export default function AuthLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
return ( return (
<html lang="ko"> <html lang="ko">
<body>{children}</body> <body>{children}</body>
</html> </html>
); );
} }
+1 -1
View File
@@ -3,7 +3,7 @@ import type { Metadata } from "next";
import { App } from "@/app"; import { App } from "@/app";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "하수도플랫폼 통합관리시스템", title: "유역하수도 통합 정보플랫폼",
}; };
export default App; export default App;
-10
View File
@@ -1,10 +0,0 @@
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 });
}
-46
View File
@@ -1,46 +0,0 @@
import type { Metadata } from "next";
import Image from "next/image";
import "@/app/styles/base.css";
import "@/app/styles/components.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>
);
}
-1
View File
@@ -7,7 +7,6 @@ const nextConfig: NextConfig = {
output: "standalone", output: "standalone",
experimental: { experimental: {
proxyTimeout: 600000, proxyTimeout: 600000,
globalNotFound: true,
}, },
eslint: { eslint: {
ignoreDuringBuilds: true, ignoreDuringBuilds: true,
@@ -92,7 +92,6 @@ async function silentRefresh(): Promise<void> {
} }
async function logout(): Promise<void> { async function logout(): Promise<void> {
await fetch("/api/auth/logout", { method: "POST" });
tokenManager.clearAuthToken(); tokenManager.clearAuthToken();
clearDeviceId(); clearDeviceId();
@@ -1,12 +1,10 @@
import { z } from "zod"; import { z } from "zod";
import type { PageableList, PageableParams } from "@/shared/model"; import type { PageableList, PageableParams } from "@/shared/model";
export interface AuthorizationsParams extends PageableParams { export interface AuthorizationsParams extends PageableParams {
keywordType?: string; keywordType?: string;
keyword?: string; keyword?: string;
authorizationName?: string; authorizationName?: string;
urlPatternValue?: string;
httpMethodCode?: string; httpMethodCode?: string;
useYn?: string; useYn?: string;
} }
+2 -19
View File
@@ -23,7 +23,6 @@ import { columns } from "../model/authorizations-table-columns";
const DEFAULT_PARAMS = { const DEFAULT_PARAMS = {
authorizationName: "", authorizationName: "",
urlPatternValue: "",
httpMethodCode: "", httpMethodCode: "",
sort: "regDt,desc", sort: "regDt,desc",
page: 0, page: 0,
@@ -37,9 +36,6 @@ function ModulePage() {
const [authorizationName, setAuthorizationName] = useState( const [authorizationName, setAuthorizationName] = useState(
params.authorizationName, params.authorizationName,
); );
const [urlPatternValue, setUrlPatternValue] = useState(
params.urlPatternValue,
);
const [httpMethodCode, setHttpMethodCode] = useState(params.httpMethodCode); const [httpMethodCode, setHttpMethodCode] = useState(params.httpMethodCode);
const query = useQuery({ const query = useQuery({
@@ -47,7 +43,6 @@ function ModulePage() {
queryFn: () => queryFn: () =>
AuthorizationsApi.getAuthorizationsList({ AuthorizationsApi.getAuthorizationsList({
authorizationName: params.authorizationName, authorizationName: params.authorizationName,
urlPatternValue: params.urlPatternValue,
httpMethodCode: params.httpMethodCode, httpMethodCode: params.httpMethodCode,
sort: params.sort, sort: params.sort,
page: params.page, page: params.page,
@@ -68,12 +63,11 @@ function ModulePage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
setParams({ authorizationName, urlPatternValue, httpMethodCode, page: 0 }); setParams({ authorizationName, httpMethodCode, page: 0 });
}; };
const handleReset = () => { const handleReset = () => {
setAuthorizationName(""); setAuthorizationName("");
setUrlPatternValue("");
setHttpMethodCode(""); setHttpMethodCode("");
resetParams(); resetParams();
}; };
@@ -119,23 +113,12 @@ function ModulePage() {
API명 API명
</Label> </Label>
<Input <Input
id="authorizationName" id="keyword"
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={authorizationName} value={authorizationName}
onChange={(e) => setAuthorizationName(e.target.value)} onChange={(e) => setAuthorizationName(e.target.value)}
/> />
</div> </div>
<div className="flex items-center gap-2">
<Label htmlFor="urlPatternValue" className="text-label">
API URL
</Label>
<Input
id="urlPatternValue"
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={urlPatternValue}
onChange={(e) => setUrlPatternValue(e.target.value)}
/>
</div>
</div> </div>
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
+11 -17
View File
@@ -1,17 +1,10 @@
"use client"; "use client";
import { useEffect, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import dayjs from "dayjs"; import dayjs from "dayjs";
import Image from "next/image";
import { ENDPOINT } from "@/shared/config"; import { useEffect, useState } from "react";
import { useAlertStore, useMenuStore, useMutationWrapper } from "@/shared/lib"; import { useRouter } from "next/navigation";
import { AttachmentApi } from "@/entities/attachment"; import { useQuery } from "@tanstack/react-query";
import { BoardApi } from "@/entities/board";
import { PostApi } from "@/entities/post";
import { import {
Button, Button,
Label, Label,
@@ -19,11 +12,14 @@ import {
RadioGroupItem, RadioGroupItem,
Skeleton, Skeleton,
} from "@common/components"; } from "@common/components";
import { AttachmentApi } from "@/entities/attachment";
import { PostEvaluationApi } from "../api/post-evaluation-service"; import { PostApi } from "@/entities/post";
import { BoardApi } from "@/entities/board";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper, useMenuStore } from "@/shared/lib";
import { PostAction } from "./post-action"; import { PostAction } from "./post-action";
import { PostComment } from "./post-comment"; import { PostComment } from "./post-comment";
import { PostEvaluationApi } from "../api/post-evaluation-service";
function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const router = useRouter(); const router = useRouter();
@@ -259,9 +255,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
)} )}
</div> </div>
</PostAction> </PostAction>
{bbsInfo?.cmntUseYn === "Y" && ( <PostComment bbsId={parseInt(bbsId)} pstNo={parseInt(pstNo)} />
<PostComment bbsId={parseInt(bbsId)} pstNo={parseInt(pstNo)} />
)}
</> </>
); );
} }
-2
View File
@@ -1,12 +1,10 @@
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
import tailwindcssAnimate from "tailwindcss-animate"; import tailwindcssAnimate from "tailwindcss-animate";
import presetConfig from "../../tailwind.config.preset"; import presetConfig from "../../tailwind.config.preset";
export default { export default {
presets: [presetConfig], presets: [presetConfig],
content: [ content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./src/**/*.{js,ts,jsx,tsx}", "./src/**/*.{js,ts,jsx,tsx}",
"../../packages/common/components/src/ui/**/*.{js,ts,jsx,tsx}", "../../packages/common/components/src/ui/**/*.{js,ts,jsx,tsx}",
], ],
+1 -1
View File
@@ -1,7 +1,7 @@
import "@/app/styles/base.css"; import "@/app/styles/base.css";
export const metadata = { export const metadata = {
title: "유역하수도 통합 정보플랫폼", title: "하수도통계 관리시스템",
}; };
export default function AuthLayout({ export default function AuthLayout({
@@ -97,7 +97,7 @@ function WithBaseLayoutInner({ children }: { children: React.ReactNode }) {
useEffect(() => { useEffect(() => {
if (!getToken(CLAIM_NAME)) return; if (!getToken(CLAIM_NAME)) return;
void statisticsApi.refreshCommonCodes().catch(() => { void statisticsApi.refreshAllCommonCodes().catch(() => {
// 캐시 동기화 실패는 화면 진입을 막지 않음 // 캐시 동기화 실패는 화면 진입을 막지 않음
}); });
}, []); }, []);
+2 -1
View File
@@ -1,8 +1,9 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { App } from "@/app"; import { App } from "@/app";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "유역하수도 통합 정보플랫폼", title: "하수도통계 관리시스템",
}; };
export default App; export default App;
+10
View File
@@ -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 });
}
+47
View File
@@ -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>
);
}
+3
View File
@@ -5,6 +5,9 @@ const subDomain =
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "standalone", output: "standalone",
experimental: {
globalNotFound: true,
},
async headers() { async headers() {
return [ return [
{ {
+1
View File
@@ -41,6 +41,7 @@
"chart.js": "^4.4.8", "chart.js": "^4.4.8",
"chartjs-plugin-zoom": "^2.2.0", "chartjs-plugin-zoom": "^2.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"decimal.js": "^10.6.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lucide-react": "^0.474.0", "lucide-react": "^0.474.0",

Before

Width:  |  Height:  |  Size: 483 KiB

After

Width:  |  Height:  |  Size: 483 KiB

+7 -5
View File
@@ -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/base.css";
import "./styles/components.css"; import "./styles/components.css";
import "@fontsource-variable/noto-sans-kr";
import "realgrid/dist/realgrid-style.css"; import "realgrid/dist/realgrid-style.css";
import "../components/css/DataGrid.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 App = async ({ children }: Readonly<{ children: React.ReactNode }>) => {
const theme = (await cookies()).get("site-theme-key"); const theme = (await cookies()).get("site-theme-key");
+12 -10
View File
@@ -1,20 +1,22 @@
"use client"; "use client";
import { DndProvider } from "react-dnd"; 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 { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 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 { AuthWrapper } from "@/entities/auth";
import { MenuWrapper } from "@/entities/menu"; 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 AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@@ -1,6 +1,7 @@
import { Button } from "@common/components";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button } from "@common/components";
interface LastYearDataButtonProps { interface LastYearDataButtonProps {
getLastYearDataFunc: () => void; getLastYearDataFunc: () => void;
} }
@@ -1,6 +1,7 @@
import { Checkbox, buttonVariants, cn } from "@common/components";
import type React from "react"; import type React from "react";
import { buttonVariants, Checkbox, cn } from "@common/components";
interface NotApplicableCheckButtonProp { interface NotApplicableCheckButtonProp {
isCheck: boolean; isCheck: boolean;
setIsCheck: React.Dispatch<React.SetStateAction<boolean>>; setIsCheck: React.Dispatch<React.SetStateAction<boolean>>;
@@ -2,15 +2,17 @@ import { Button } from "@common/components";
interface SubmitButtonProps { interface SubmitButtonProps {
submitFunc: () => void; submitFunc: () => void;
/** 버튼 문구 (기본: 제출) */
text?: string;
} }
const SubmitButton = ({ submitFunc }: SubmitButtonProps) => { const SubmitButton = ({ submitFunc, text = "제출" }: SubmitButtonProps) => {
return ( 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" /> <i className="diveicon di-upload" />
<span>제출</span> <span>{text}</span>
</Button> </Button>
); );
}; };
export default SubmitButton; export default SubmitButton;
+49
View File
@@ -1,5 +1,22 @@
@tailwind components; @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 { @layer components {
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell { .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell {
@apply !bg-white; @apply !bg-white;
@@ -8,6 +25,15 @@
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly { .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly {
@apply !bg-[#f0f0f0] !text-[#666]; @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 { .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-rowtype-curr {
@apply !bg-white !text-[#333]; @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 { .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-verify-error-imr {
@apply !bg-[#fff7bf] !text-[#b45309]; @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 미처리 등) */ /* 검증결과 내역: 처리여부(prcsYn 0·4 미처리 등) */
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-vrf-prcs-bad { .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-vrf-prcs-bad {
@apply !font-semibold !text-[#dc2626]; @apply !font-semibold !text-[#dc2626];
@@ -399,6 +434,13 @@
background-color: #ffe6e6 !important; background-color: #ffe6e6 !important;
color: #c00 !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-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-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, .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; background-color: #fff7bf !important;
color: #b45309 !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에서도 검증 오류 색을 유지하고 싶다면 동일 색 유지(가독성 우선) */ /* dark theme에서도 검증 오류 색을 유지하고 싶다면 동일 색 유지(가독성 우선) */
[data-theme="black"] .rg-grid .rg-body .rg-data-cell.rg-cell-verify-error.rg-cell-verify-error, [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 { useEffect, useLayoutEffect,useRef } from "react"
import { ValueType } from "realgrid"
import { ValueType } from "realgrid"
import RealGrid, { GridFitStyle } from "realgrid" import RealGrid, { GridFitStyle } from "realgrid"
import { RealGridReact, RGDataColumn } from "realgrid-react" 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 { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
import { wrapWithFormatTruncate } from "@/components/util/grid/decimalNumber"
function mergeCellStyleCallbackResult( function mergeCellStyleCallbackResult(
baseStyleName: string | undefined, baseStyleName: string | undefined,
@@ -30,7 +33,7 @@ declare module "realgrid" {
setOptions(options: { setOptions(options: {
footer?: { visible?: boolean }; footer?: { visible?: boolean };
summaryMode?: 'aggregate' | 'statistical' | 'none'; summaryMode?: 'aggregate' | 'statistical' | 'none';
checkBar?: { visible?: boolean }; checkBar?: { visible?: boolean; checkableOnly?: boolean };
}): void; }): void;
} }
} }
@@ -47,10 +50,11 @@ type GridViewWithCheckApi = GridViewWithSetColumns & {
checkItem?: (itemIndex: number, checked: boolean, exclusive?: boolean) => void; checkItem?: (itemIndex: number, checked: boolean, exclusive?: boolean) => void;
checkRow?: (dataRow: number, checked: boolean, exclusive?: boolean) => void; checkRow?: (dataRow: number, checked: boolean, exclusive?: boolean) => void;
checkAll?: (checked: boolean) => void; checkAll?: (checked: boolean) => void;
setCheckable?: (itemIndex: number, checkable: boolean) => void;
}; };
/** columnInfo → RealGrid DataField 배열 (computed → valueCallback 포함, setFields용) */ /** 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) => { return columns.map((col) => {
const field: { fieldName: string; dataType: string; valueCallback?: (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number } = { const field: { fieldName: string; dataType: string; valueCallback?: (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number } = {
fieldName: col.fieldName, 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 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); 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) { 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; return field;
@@ -136,6 +140,7 @@ const CompactDataGrid = ({
onRowClick, onRowClick,
onItemChecked, onItemChecked,
onItemAllChecked, onItemAllChecked,
checkableCallback,
compactFullWidth = false, compactFullWidth = false,
compactGridHeightClass = "h-[55vh]", compactGridHeightClass = "h-[55vh]",
headerHeight = 60, headerHeight = 60,
@@ -145,9 +150,11 @@ const CompactDataGrid = ({
const columnsRef = useRef(columns); const columnsRef = useRef(columns);
const rowDataRef = useRef<Record<string, string | number>[]>(rowData); const rowDataRef = useRef<Record<string, string | number>[]>(rowData);
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(cellStyleCallback); const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(cellStyleCallback);
const checkableCallbackRef = useRef<DataGridProps["checkableCallback"]>(checkableCallback);
columnsRef.current = columns; columnsRef.current = columns;
rowDataRef.current = rowData; rowDataRef.current = rowData;
cellStyleCallbackRef.current = cellStyleCallback; cellStyleCallbackRef.current = cellStyleCallback;
checkableCallbackRef.current = checkableCallback;
useLayoutEffect(() => { useLayoutEffect(() => {
if (!cellStyleCallback || !gridRef.current?.gridView) return; if (!cellStyleCallback || !gridRef.current?.gridView) return;
@@ -176,8 +183,10 @@ const CompactDataGrid = ({
try { try {
checkGrid.checkAll?.(false); checkGrid.checkAll?.(false);
rowData.forEach((row, itemIndex) => { rowData.forEach((row, itemIndex) => {
const checkable = checkableCallbackRef.current?.(row, itemIndex) ?? true;
checkGrid.setCheckable?.(itemIndex, checkable);
const checked = checkByRowFlag((row as Record<string, unknown>).__checked); const checked = checkByRowFlag((row as Record<string, unknown>).__checked);
if (!checked) return; if (!checked || !checkable) return;
if (checkGrid.checkItem) { if (checkGrid.checkItem) {
checkGrid.checkItem(itemIndex, true, false); checkGrid.checkItem(itemIndex, true, false);
return; return;
@@ -274,7 +283,7 @@ const CompactDataGrid = ({
// 헤더 체크박스 표시 여부 설정 // 헤더 체크박스 표시 여부 설정
gridView.setOptions({ gridView.setOptions({
checkBar: { visible: showHeaderCheckbox }, checkBar: { visible: showHeaderCheckbox, checkableOnly: true },
rowIndicator: { visible: false }, rowIndicator: { visible: false },
}); });
@@ -306,6 +315,7 @@ const CompactDataGrid = ({
if (fixedColumns !== undefined && fixedColumns > 0) { if (fixedColumns !== undefined && fixedColumns > 0) {
gridView.setFixedOptions({ gridView.setFixedOptions({
colCount: fixedColumns, colCount: fixedColumns,
resizable: true,
}); });
} }
@@ -496,6 +506,7 @@ const CompactDataGrid = ({
compactFullWidth, compactFullWidth,
compactGridHeightClass, compactGridHeightClass,
headerHeight, headerHeight,
checkableCallback,
]); ]);
// 행 클릭 이벤트 // 행 클릭 이벤트
@@ -524,6 +535,17 @@ const CompactDataGrid = ({
if (onItemChecked) { if (onItemChecked) {
gridView.onItemChecked = (_grid, itemIndex, checked) => { 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); onItemChecked(itemIndex, checked);
}; };
} else { } 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 { RealGridReact } from "realgrid-react";
import { getUserInfo } from "../../../../../packages/common/components/src/api";
import { useAlertStore } from "@/shared/lib"; import { useAlertStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { import type {
@@ -29,6 +28,8 @@ import {
Textarea, Textarea,
} from "@common/components"; } from "@common/components";
import { getUserInfo } from "../../../../../packages/common/components/src/api";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination"; import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type"; import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
@@ -56,8 +57,8 @@ type ExceptionHistoryTabKey = "NV" | "IMR" | "HB";
const exceptionHistoryTabs: { value: ExceptionHistoryTabKey; label: string }[] = [ const exceptionHistoryTabs: { value: ExceptionHistoryTabKey; label: string }[] = [
{ value: "NV", label: "일반검증 내역" }, { value: "NV", label: "일반검증 내역" },
{ value: "IMR", label: "I-MR 검증내역" }, { value: "IMR", label: "시계열 검증내역" },
{ value: "HB", label: "HB 검증내역" }, { value: "HB", label: "모집단 검증내역" },
]; ];
const TITLE = "예외처리 내역 조회"; const TITLE = "예외처리 내역 조회";
@@ -69,7 +70,7 @@ export type ExceptionRejectSubmitItem = {
statsYr: string; statsYr: string;
sewerBzmnCd: string; sewerBzmnCd: string;
statsCd: string; statsCd: string;
mdfrId: string; mdfrUuid: string;
rjctOpnnCn?: string; rjctOpnnCn?: string;
}; };
@@ -83,8 +84,8 @@ function prcsYnCodeToNumber(raw: string | number | undefined): number | undefine
} }
function exceptionPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined { function exceptionPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined {
if (fieldName !== "prcsYnNm") return undefined; if (fieldName !== "vrfcSttsNm") return undefined;
const code = prcsYnCodeToNumber(row.prcsYn); const code = prcsYnCodeToNumber(row.vrfcSttsCd);
if (code === 0 || code === 4) return "rg-cell-vrf-prcs-bad"; if (code === 0 || code === 4) return "rg-cell-vrf-prcs-bad";
return "rg-cell-vrf-prcs-ok"; return "rg-cell-vrf-prcs-ok";
} }
@@ -95,10 +96,10 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false }, { name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false },
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false }, { name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false }, { name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false }, { name: "nowCn", fieldName: "nowCn", header: { text: "입력값" }, editable: false },
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false }, { name: "cmprCn", fieldName: "cmprCn", header: { text: "기준값" }, editable: false },
{ name: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false }, { name: "vrfcSttsNm", fieldName: "vrfcSttsNm", header: { text: "처리여부" }, editable: false },
{ name: "stVrfRjtComNm", fieldName: "stVrfRjtComNm", header: { text: "반려상태" }, editable: false }, { name: "statsVrfcRjtComNm", fieldName: "statsVrfcRjtComNm", header: { text: "반려상태" }, editable: false },
{ name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false }, { name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false },
{ name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false }, { name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false },
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false }, { name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
@@ -111,7 +112,7 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
width: 104, width: 104,
styleName: "rg-cell-reject-mini-btn", 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 }, { name: "vrfcSn", fieldName: "vrfcSn", header: { text: "vrfcSn" }, editable: false, visible: false },
], ],
columnLayout: [ columnLayout: [
@@ -119,16 +120,16 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
"admdstNm", "admdstNm",
"vrfcTrgtNm", "vrfcTrgtNm",
"rsnExpln", "rsnExpln",
"nowVlCn", "nowCn",
"compVlCn", "cmprCn",
"prcsYnNm", "vrfcSttsNm",
"stVrfRjtComNm", "statsVrfcRjtComNm",
"prcsOpnnExpln", "prcsOpnnExpln",
"rjctOpnnCn", "rjctOpnnCn",
"regDt", "regDt",
"mdfcnDt", "mdfcnDt",
"individualReject", "individualReject",
"prcsYn", "vrfcSttsCd",
"vrfcSn", "vrfcSn",
], ],
}; };
@@ -274,7 +275,7 @@ export function ExceptionProcessingHistoryDialog({
setImrPageCount(response?.data?.pageInfo?.totalPages ?? 1); setImrPageCount(response?.data?.pageInfo?.totalPages ?? 1);
setImrPageIndex(pageIndex); setImrPageIndex(pageIndex);
} catch { } catch {
setErrorMessage("I-MR 검증내역 조회 중 오류가 발생했습니다."); setErrorMessage("시계열 검증내역 조회 중 오류가 발생했습니다.");
} finally { } finally {
setImrLoading(false); setImrLoading(false);
} }
@@ -293,7 +294,7 @@ export function ExceptionProcessingHistoryDialog({
setHbPageCount(response?.data?.pageInfo?.totalPages ?? 1); setHbPageCount(response?.data?.pageInfo?.totalPages ?? 1);
setHbPageIndex(pageIndex); setHbPageIndex(pageIndex);
} catch { } catch {
setErrorMessage("HB 검증내역 조회 중 오류가 발생했습니다."); setErrorMessage("모집단 검증내역 조회 중 오류가 발생했습니다.");
} finally { } finally {
setHbLoading(false); setHbLoading(false);
} }
@@ -315,13 +316,13 @@ export function ExceptionProcessingHistoryDialog({
admdstNm: item.admdstNm ?? item.sggNm ?? "-", admdstNm: item.admdstNm ?? item.sggNm ?? "-",
vrfcTrgtNm: item.vrfcTrgtNm, vrfcTrgtNm: item.vrfcTrgtNm,
rsnExpln: item.rsnExpln, rsnExpln: item.rsnExpln,
nowVlCn: item.nowVlCn ?? "-", nowCn: item.nowCn ?? "-",
compVlCn: item.compVlCn ?? "-", cmprCn: item.cmprCn ?? "-",
prcsYnNm: item.prcsYnNm ?? "-", vrfcSttsNm: item.vrfcSttsNm ?? "-",
stVrfRjtComNm: item.stVrfRjtComNm ?? "-", statsVrfcRjtComNm: item.statsVrfcRjtComNm ?? "-",
prcsOpnnExpln: item.prcsOpnnExpln ?? "-", prcsOpnnExpln: item.prcsOpnnExpln ?? "-",
rjctOpnnCn: item.rjctOpnnCn ?? "-", rjctOpnnCn: item.rjctOpnnCn ?? "-",
prcsYn: item.prcsYn ?? "", vrfcSttsCd: item.vrfcSttsCd ?? "",
regDt: (item.regDt ?? "").slice(0, 16), regDt: (item.regDt ?? "").slice(0, 16),
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16), mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
individualReject: "개별 반려", individualReject: "개별 반려",
@@ -400,7 +401,7 @@ export function ExceptionProcessingHistoryDialog({
alert("사용자 정보를 불러오지 못했습니다."); alert("사용자 정보를 불러오지 못했습니다.");
return; return;
} }
const mdfrId = userInfo.userUnqId; const mdfrUuid = userInfo.userUnqId;
const trimmedReason = rejectReasonText.trim(); const trimmedReason = rejectReasonText.trim();
const items: ReviewInputSaveRequest[] = pendingRejectVrfcSns.map((vrfcSn) => ({ const items: ReviewInputSaveRequest[] = pendingRejectVrfcSns.map((vrfcSn) => ({
@@ -408,7 +409,7 @@ export function ExceptionProcessingHistoryDialog({
statsYr: queryContext.statsYr, statsYr: queryContext.statsYr,
sewerBzmnCd: queryContext.sewerBzmnCd, sewerBzmnCd: queryContext.sewerBzmnCd,
statsCd: queryContext.statsCd, statsCd: queryContext.statsCd,
mdfrId, mdfrUuid,
vrfcSn: String(vrfcSn), vrfcSn: String(vrfcSn),
...(trimmedReason ? { rjctOpnnCn: trimmedReason } : {}), ...(trimmedReason ? { rjctOpnnCn: trimmedReason } : {}),
})); }));
@@ -1,11 +1,13 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@common/components"; import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@common/components";
import SidoSggFacilityNameSearchForm from "./SidoSggFacilityNameSearchForm";
import type { SearchFormValues } from "../type/SearchForm.type"; import type { SearchFormValues } from "../type/SearchForm.type";
import SidoSggFacilityNameSearchForm from "./SidoSggFacilityNameSearchForm";
type GridTopActionButtonProps = { type GridTopActionButtonProps = {
text: string; text: string;
onClick?: (helpers: { openDialog: (title?: string) => void; closeDialog: () => void }) => void; onClick?: (helpers: { openDialog: (title?: string) => void; closeDialog: () => void }) => void;
+162 -53
View File
@@ -5,6 +5,11 @@ import { useEffect, useState } from "react";
import useSWR from "swr"; import useSWR from "swr";
import { useAlertStore, useUserStore } from "@/shared/lib"; 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 { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { import type {
ApiResponse, ApiResponse,
@@ -23,28 +28,43 @@ import {
SelectValue, SelectValue,
} from "@common/components"; } from "@common/components";
import { doList, doNameList } from "@/components/util/column/input/DoList";
import { import {
defaultSearchFormProps, defaultSearchFormProps,
type SearchFormClsfCd, type SearchFormClsfCd,
type SearchFormProps, type SearchFormProps,
type SearchFormValues, type SearchFormValues,
} from "../type/SearchForm.type"; } from "../type/SearchForm.type";
import { doList } from "../util/column/input/DoList";
const getSidoListFetcher = async ( function isDoListBsnm(bsnmCd: string | undefined): boolean {
key: [string, string], return Boolean(bsnmCd && doList.includes(bsnmCd));
): Promise<ApiResponse<SearchCondition>> => { }
return statisticsApi.getSidoList({ instSeCd: key[1] });
}; 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 ( const getSggListFetcher = async (
key: [string, string, string, string], key: [string, string, string, "Y" | "N"],
): Promise<ApiResponse<Sgg[]>> => { ): Promise<ApiResponse<Sgg[]>> => {
const gugunSkipYn = doList.includes(key[3]) ? "N" : "Y"; const [kind, sidoName, instSeCd, gugunSkipYn] = key;
return statisticsApi.getSggList(key[1], { const params = { instSeCd, gugunSkipYn };
instSeCd: key[2], if (kind === "sgg-all") {
gugunSkipYn, return statisticsApi.getSggAllList(sidoName, params);
}); }
return statisticsApi.getSggList(sidoName, params);
}; };
function buildSearchFormValues( function buildSearchFormValues(
@@ -59,9 +79,15 @@ function buildSearchFormValues(
: "ALL"; : "ALL";
if (d?.sido) { if (d?.sido) {
const sidoItem = sidoList?.data?.sidoList?.find(
(item) => item.sido === d.sido,
);
const gugun =
d.gugun ||
(sidoItem && !isDoSido(sidoItem) ? "ALL" : "");
return { return {
sido: d.sido, sido: d.sido,
gugun: d.gugun || "", gugun,
year: d.year || defaultYear.toString(), year: d.year || defaultYear.toString(),
order: d.order || "", order: d.order || "",
fctName: d.fctName || "", 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 first = sidoList?.data?.sidoList?.[0];
const useFirstSido = (props.sido ?? defaultSearchFormProps.sido) && first; const useFirstSido = (props.sido ?? defaultSearchFormProps.sido) && first;
return { return {
sido: useFirstSido ? first.sido : "", sido: useFirstSido ? first.sido : "",
gugun: "", gugun:
useFirstSido && first && !isDoSido(first) ? "ALL" : "",
year: d?.year || defaultYear.toString(), year: d?.year || defaultYear.toString(),
order: d?.order || "", order: d?.order || "",
fctName: d?.fctName || "", fctName: d?.fctName || "",
@@ -102,10 +144,15 @@ const SearchForm = (props: SearchFormProps = {}) => {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const defaultYear = currentYear - 1; 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>>( const { data: sidoList } = useSWR<ApiResponse<SearchCondition>>(
["sido-list", instSeCd], SEARCH_CONDITION_SWR_KEY(instSeCd, sidoListKind),
getSidoListFetcher, isInput ? fetchSearchConditionInput : fetchSearchCondition,
{ {
revalidateOnFocus: false, revalidateOnFocus: false,
revalidateOnReconnect: false, revalidateOnReconnect: false,
@@ -132,6 +179,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
// 최초 진입만 시도 API가 늦게 오는 경우 // 최초 진입만 시도 API가 늦게 오는 경우
useEffect(() => { useEffect(() => {
if (props.defaultRegionAll) return;
if (props.defaultValues?.sido) return; if (props.defaultValues?.sido) return;
const first = sidoList?.data?.sidoList?.[0]; const first = sidoList?.data?.sidoList?.[0];
if (!first || !mergedProps.sido) return; if (!first || !mergedProps.sido) return;
@@ -139,17 +187,33 @@ const SearchForm = (props: SearchFormProps = {}) => {
prev.sido prev.sido
? prev ? prev
: { : {
...prev, ...prev,
sido: first.sido, sido: first.sido,
gugun: "", gugun: isDoSido(first) ? "" : "ALL",
bsnmCd: first.bsnmCd, bsnmCd: first.bsnmCd,
}, },
); );
}, [sidoList, props.defaultValues?.sido, mergedProps.sido]); }, [
sidoList,
props.defaultValues?.sido,
props.defaultRegionAll,
mergedProps.sido,
]);
const sggKey: [string, string, string, string] | null = const selectedSido = sidoList?.data?.sidoList?.find(
values.sido && values.sido !== "ALL" (item) => item.sido === values.sido,
? ["sgg-list", values.sido, instSeCd, values.bsnmCd || ""] );
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; : null;
const { data: sggList } = useSWR<ApiResponse<Sgg[]>>( const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
sggKey, sggKey,
@@ -163,39 +227,60 @@ const SearchForm = (props: SearchFormProps = {}) => {
onSuccess: (data) => { onSuccess: (data) => {
if (data.status !== "success") { if (data.status !== "success") {
alert(`시군구 목록을 불러오는데 실패했습니다. alert(`시군구 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`); 새로고침 후 다시 시도해주세요.`);
} }
}, },
onError: () => { onError: () => {
alert(`시군구 목록을 불러오는데 실패했습니다. alert(`시군구 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`); 새로고침 후 다시 시도해주세요.`);
}, },
}, },
); );
const sggOptions = sggList?.data ?? []; 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(() => { useEffect(() => {
if (!mergedProps.gugun) return; if (!mergedProps.gugun) return;
if (props.defaultValues?.gugun) return; if (props.defaultRegionAll) return;
if (!values.sido || values.sido === "ALL") return; if (!values.sido || values.sido === "ALL") return;
if (!selectedSido) return;
const firstSgg = sggList?.data?.[0]; if (!isDoSelected) {
if (!firstSgg) return; setValues((prev) => {
if (prev.gugun) return prev;
return {
...prev,
gugun: "ALL",
bsnmCd: selectedSido.bsnmCd || prev.bsnmCd,
};
});
return;
}
if (!firstConcreteSggOption) return;
setValues((prev) => { setValues((prev) => {
if (prev.gugun) return prev; if (prev.gugun) return prev;
return { return {
...prev, ...prev,
gugun: firstSgg.gugun, gugun: firstConcreteSggOption.gugun,
bsnmCd: firstSgg.bsnmCd, bsnmCd: firstConcreteSggOption.bsnmCd,
}; };
}); });
}, [ }, [
sggList, firstConcreteSggOption,
values.sido, isDoSelected,
mergedProps.gugun, mergedProps.gugun,
props.defaultValues?.gugun, props.defaultRegionAll,
selectedSido,
values.sido,
]); ]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
@@ -209,12 +294,17 @@ const SearchForm = (props: SearchFormProps = {}) => {
}; };
const handleSidoChange = (value: string) => { const handleSidoChange = (value: string) => {
const selectedBsnm = const selected =
sidoList?.data?.sidoList?.find((v) => v.sido === value)?.bsnmCd || ""; 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) => ({ setValues((prev) => ({
...prev, ...prev,
sido: value, sido: value,
gugun: "", gugun: gugunAll ? "ALL" : "",
bsnmCd: selectedBsnm, bsnmCd: selectedBsnm,
})); }));
}; };
@@ -224,7 +314,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
const selectedBsnm = const selectedBsnm =
value === "ALL" value === "ALL"
? sidoList?.data?.sidoList?.find((v) => v.sido === values.sido) ? sidoList?.data?.sidoList?.find((v) => v.sido === values.sido)
?.bsnmCd || "" ?.bsnmCd || ""
: listForBsnm.find((v) => v.gugun === value)?.bsnmCd || ""; : listForBsnm.find((v) => v.gugun === value)?.bsnmCd || "";
setValues((prev) => ({ setValues((prev) => ({
@@ -274,7 +364,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Label className="text-label">시군구</Label> <Label className="text-label">시군구</Label>
<Select <Select
value={values.gugun} value={gugunSelectValue}
onValueChange={handleSggChange} onValueChange={handleSggChange}
> >
<SelectTrigger className="w-[12rem]"> <SelectTrigger className="w-[12rem]">
@@ -377,20 +467,27 @@ const SearchForm = (props: SearchFormProps = {}) => {
<SelectValue placeholder="전체"></SelectValue> <SelectValue placeholder="전체"></SelectValue>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value='ALL'>전체</SelectItem> <SelectItem value="ALL">전체</SelectItem>
<SelectItem value='500톤이상'>500톤이상</SelectItem> <SelectItem value="500톤이상">
<SelectItem value='500톤미만'>500톤미만</SelectItem> 500톤이상
</SelectItem>
<SelectItem value="500톤미만">
500톤미만
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
)} )}
{mergedProps.isDetail && ( {mergedProps.isDetail && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Label className="text-label cursor-pointer" htmlFor="isDetail"> <Label
className="cursor-pointer text-label"
htmlFor={isDetailInputId}
>
세부 데이터 포함 세부 데이터 포함
</Label> </Label>
<Checkbox <Checkbox
id="isDetail" id={isDetailInputId}
checked={values.isDetail} checked={values.isDetail}
onCheckedChange={(value) => onCheckedChange={(value) =>
setValues({ setValues({
@@ -403,7 +500,11 @@ const SearchForm = (props: SearchFormProps = {}) => {
)} )}
{mergedProps.isSummary && ( {mergedProps.isSummary && (
<div className="flex items-center gap-2"> <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> </Label>
<Select <Select
@@ -416,11 +517,17 @@ const SearchForm = (props: SearchFormProps = {}) => {
<SelectValue placeholder="선택 안함"></SelectValue> <SelectValue placeholder="선택 안함"></SelectValue>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="ALL">선택 안함</SelectItem> <SelectItem value="ALL">
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => ( 선택 안함
<SelectItem value={i.toString()} key={i.toString()}>{`${i}개년`}</SelectItem> </SelectItem>
)) {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(
} (i) => (
<SelectItem
value={i.toString()}
key={i.toString()}
>{`${i}개년`}</SelectItem>
),
)}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@@ -455,7 +562,9 @@ const SearchForm = (props: SearchFormProps = {}) => {
</Button> </Button>
)} )}
<Button className="h-[3.2rem] w-[96px]" type="submit"> <Button className="h-[3.2rem] w-[96px]" type="submit">
{props.searchButtonText ? props.searchButtonText : '조회'} {props.searchButtonText
? props.searchButtonText
: "조회"}
</Button> </Button>
</div> </div>
</div> </div>
@@ -1,41 +1,42 @@
"use client"; "use client";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import useSWR from "swr";
import { ValueType } from "realgrid";
import { import { ValueType } from "realgrid";
Button, import useSWR from "swr";
Input,
Label,
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
LoadingWithAnimation,
} from "@common/components";
import CompactDataGrid from "./CompactDataGrid";
import SaveButton from "../button/SaveButton";
import { useAlertStore } from "@/shared/lib"; 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 { 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 { import type {
ApiResponse, ApiResponse,
SearchCondition, SearchCondition,
Sgg, Sgg,
} from "@/entities/statistics/model/statistics-interface"; } from "@/entities/statistics/model/statistics-interface";
import type { ColumnInfo } from "../type/DataGrid.type"; import {
import type { Button,
SeweragePipeFacilitiesListRequest, Input,
SeweragePipeFacilityAdminAreaListRequest, Label,
SeweragePipeFactConnectionSaveRequest, LoadingWithAnimation,
SeweragePipeFactConnectionSaveItem, Select,
} from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface"; SelectContent,
import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service"; SelectItem,
import type { SearchFormValues } from "../type/SearchForm.type"; SelectTrigger,
import { doNameList } from "../util/column/input/DoList"; SelectValue,
} from "@common/components";
import { getUserInfo } from "../../../../../packages/common/components/src/api"; 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 = { const treatmentFacilityColumnInfo: ColumnInfo = {
@@ -114,9 +115,9 @@ const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
}; };
const getSggAllListFetcher = async ( const getSggAllListFetcher = async (
key: [string, string], key: [string, string, "Y" | "N"],
): Promise<ApiResponse<Sgg[]>> => { ): Promise<ApiResponse<Sgg[]>> => {
return statisticsApi.getSggList(key[1]); return statisticsApi.getSggList(key[1], { gugunSkipYn: key[2] });
}; };
type SidoSggFacilityNameSearchFormProps = { type SidoSggFacilityNameSearchFormProps = {
@@ -182,9 +183,16 @@ const SidoSggFacilityNameSearchForm = ({
Record<string, string | number> | null Record<string, string | number> | null
>(null); >(null);
const didAutoFetchRef = useRef(false); const didAutoFetchRef = useRef(false);
const isSavingConnectionRef = useRef(false);
const sggKey: [string, string] | null = const selectedSido = sidoList?.data?.sidoList?.find(
sido && sido !== "ALL" ? ["sgg-all", sido] : null; (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[]>>( const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
sggKey, sggKey,
getSggAllListFetcher, getSggAllListFetcher,
@@ -297,7 +305,11 @@ const SidoSggFacilityNameSearchForm = ({
swtrmtFcltCd, swtrmtFcltCd,
sewerBzmnCd: initialSearchValues?.bsnmCd || '', sewerBzmnCd: initialSearchValues?.bsnmCd || '',
ctpvNm: initialSidoValue || '', ctpvNm: initialSidoValue || '',
...(doNameList.includes(initialSidoValue) ? { sggNm: initialGugunValue } : {}), ...(isGugunRequiredSido(sidoList?.data?.sidoList, {
sido: initialSidoValue,
})
? { sggNm: initialGugunValue }
: {}),
}; };
try { try {
@@ -329,119 +341,213 @@ const SidoSggFacilityNameSearchForm = ({
} }
}; };
const handleSaveAdminRegions = async () => { const canEditFactConnection = async (): Promise<boolean> => {
const statusCodeResponse = await statisticsApi.CheckStatusCode({ const statusCodeResponse = await statisticsApi.CheckStatusCode({
sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "", sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "",
statsCd: 'ST00020002', statsCd: "ST00020002",
statsYr: statsYr?.trim() ?? '' statsYr: statsYr?.trim() ?? "",
}); });
if (statusCodeResponse.status === 'success') { if (statusCodeResponse.status === "success") {
if (!['0', '1', '4'].includes(statusCodeResponse.data)) { if (!["0", "1", "4"].includes(statusCodeResponse.data)) {
const codeName = statusCodeResponse.data === '2' ? '제출' : (statusCodeResponse.data === '3' ? '승인' : '검증중'); const codeName =
alert(`시설별 설치 현황 데이터가 ${codeName} 상태일 때는 처리 시설 정보를 변경할 수 없습니다.`); statusCodeResponse.data === "2"
return; ? "제출"
} : statusCodeResponse.data === "3"
} else { ? "승인"
alert('오류가 발생했습니다.\n관리자에게 문의하세요') : "검증중";
alert(
`시설별 설치 현황 데이터가 ${codeName} 상태일 때는 처리 시설 정보를 변경할 수 없습니다.`,
);
return false;
}
return true;
} }
alert("오류가 발생했습니다.\n관리자에게 문의하세요");
return false;
};
const persistFactConnection = async (
saveList: SeweragePipeFactConnectionSaveItem[],
deleteList: string[],
): Promise<boolean> => {
if (!statsYr?.trim()) { if (!statsYr?.trim()) {
alert("상단에서 통계연도로 조회한 뒤 이용해 주세요."); alert("상단에서 통계연도로 조회한 뒤 이용해 주세요.");
return; return false;
} }
if (!selectedFacilityRow) { if (!selectedFacilityRow) {
alert("처리시설을 먼저 선택해주세요."); alert("처리시설을 먼저 선택해주세요.");
return; return false;
}
if (saveList.length === 0 && deleteList.length === 0) {
return true;
}
if (!(await canEditFactConnection())) {
return false;
} }
const hasChanged = adminRegionRows.some( try {
(row) => setIsLoading(true);
(row.__checked === "1" && row.isConnection !== "1") || const userInfo = await getUserInfo();
(row.__checked !== "1" && row.isConnection === "1"), 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 const response =
.filter((row) => row.__checked === "1") await statisticsInputSewPipeApiService.saveFactConnection(
.map((row) => ({ request,
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",
})),
); );
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, itemIndex: number,
checked: boolean, 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) => setAdminRegionRows((prev) =>
prev.map((row, idx) => prev.map((row, idx) =>
idx === itemIndex idx === itemIndex
? { ...row, __checked: checked ? "1" : "0" } ? { ...row, __checked: nextChecked }
: row, : 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) => 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(() => { useEffect(() => {
@@ -575,8 +681,10 @@ const SidoSggFacilityNameSearchForm = ({
조회 조회
</Button> </Button>
</div> </div>
<div className="ml-auto"> <div>
<SaveButton saveFunc={handleSaveAdminRegions} /> <span className="text-lg text-gray-500 float-end">
* 체크 박스를 선택 / 해제 시 반영됩니다.
</span>
</div> </div>
</form> </form>
{isLoading && ( {isLoading && (
@@ -16,6 +16,7 @@ const SplitDataGrid = ({
leftCellStyleCallback, leftCellStyleCallback,
rightOnItemChecked, rightOnItemChecked,
rightOnItemAllChecked, rightOnItemAllChecked,
rightCheckableCallback,
enableSort = false, enableSort = false,
}: SplitDataGridProps) => { }: SplitDataGridProps) => {
return ( return (
@@ -39,6 +40,7 @@ const SplitDataGrid = ({
setRowData={setRightRowData} setRowData={setRightRowData}
onItemChecked={rightOnItemChecked} onItemChecked={rightOnItemChecked}
onItemAllChecked={rightOnItemAllChecked} onItemAllChecked={rightOnItemAllChecked}
checkableCallback={rightCheckableCallback}
/> />
</div> </div>
); );
+109 -31
View File
@@ -18,32 +18,48 @@ function mergeCellStyleCallbackResult(
baseStyleName: string | undefined, baseStyleName: string | undefined,
extra: CellStyleCallbackResult | undefined, extra: CellStyleCallbackResult | undefined,
): CellStyleCallbackResult { ): CellStyleCallbackResult {
let styleName: string | undefined;
let extraObj: Extract<CellStyleCallbackResult, object> | undefined;
if (!extra) { 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(" "); if (!styleName && !extraObj) return {};
return styleName ? { styleName } : {};
} const result: CellStyleCallbackResult = {
const styleName = [extra.styleName, baseStyleName].filter(Boolean).join(" "); ...extraObj,
return {
...extra,
...(styleName ? { styleName } : {}), ...(styleName ? { styleName } : {}),
}; };
return result;
} }
import SplitDataGrid from "./SplitDataGrid"; import SplitDataGrid from "./SplitDataGrid";
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks" import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
import { wrapWithFormatTruncate } from "@/components/util/grid/decimalNumber"
import { import {
buildExcelExportFileBaseName, buildExcelExportFileBaseName,
buildFixedColumnNameSet,
getExcelImportColumnFieldNames, getExcelImportColumnFieldNames,
getNonEditableColumnFieldNames, getNonEditableColumnFieldNames,
hasAdmdstCdColumn, hasAdmdstCdColumn,
isExcelImportAdmdstCdInvalid, isExcelImportAdmdstCdInvalid,
mergeExcelImportWithExistingRows, mergeExcelImportWithExistingRows,
prepareGridColumnsForExcelExport, prepareGridColumnsForExcelExport,
releaseFixedOptionsForExcelExport,
resolveColumnStyleName,
restoreFixedOptionsAfterExcelExport,
restoreGridColumnsAfterExcelExport, restoreGridColumnsAfterExcelExport,
shouldPrepareColumnsForExcelExport, shouldPrepareColumnsForExcelExport,
disableAlternateRowStyle,
} from "@/components/util/gridExcelExport" } from "@/components/util/gridExcelExport"
import { useTabStore } from "@/store/tab-store" import { useTabStore } from "@/store/tab-store"
@@ -88,7 +104,7 @@ function safeGridViewRefresh(gv: { refresh?: () => void } | null | undefined) {
try { try {
gv.refresh(); gv.refresh();
} catch { } catch {
/* noop */ console.error('refresh error');
} }
} }
@@ -237,7 +253,7 @@ function sortRowsGroupPreserving(
} }
/** columnInfo → RealGrid DataField 배열 (computed → valueCallback 포함, setFields용) */ /** 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) => { return columns.map((col) => {
const field: { fieldName: string; dataType: string; valueCallback?: (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number } = { const field: { fieldName: string; dataType: string; valueCallback?: (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number } = {
fieldName: col.fieldName, 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 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); 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) { 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; return field;
@@ -282,7 +298,7 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string
const TabDataGrid = ({ const TabDataGrid = ({
columnInfo, columnInfo,
isExcelImport = true, isExcelImport = true,
excelImportSkipNonEditable = true, excelImportSkipNonEditable = false,
excelImportSewerBzmnCd, excelImportSewerBzmnCd,
excelExportSearchContext, excelExportSearchContext,
isExcelExport = true, isExcelExport = true,
@@ -313,17 +329,23 @@ const TabDataGrid = ({
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(undefined); const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(undefined);
cellStyleCallbackRef.current = cellStyleCallback; cellStyleCallbackRef.current = cellStyleCallback;
const attachCellStyleCallbacksForTab = useCallback((tabKey: string, columns: ColumnConfig[], attempt = 0) => { const attachCellStyleCallbacksForTab = useCallback((tabKey: string, columns: ColumnConfig[], columnLayout: ColumnLayoutItem[] | undefined, fixedCols: number | undefined, attempt = 0) => {
if (!cellStyleCallbackRef.current) return;
const el = gridRefs.current.get(tabKey); const el = gridRefs.current.get(tabKey);
const gv = el?.gridView; const gv = el?.gridView;
if (!gv) return; if (!gv) return;
const fixedColumnNameSet = buildFixedColumnNameSet(columns, columnLayout, fixedCols);
const hasUserCellStyleCallback = Boolean(cellStyleCallbackRef.current);
if (fixedColumnNameSet.size === 0 && !hasUserCellStyleCallback) return;
let attachedCount = 0; let attachedCount = 0;
for (const column of columns) { for (const column of columns) {
const isFixedColumn = fixedColumnNameSet.has(column.name);
if (!isFixedColumn && !hasUserCellStyleCallback) continue;
const gridCol = safeColumnByName(gv, column.name); const gridCol = safeColumnByName(gv, column.name);
if (!gridCol) continue; if (!gridCol) continue;
const baseStyleName = column.styleName; const baseStyleName = resolveColumnStyleName(column, fixedColumnNameSet);
gridCol.styleCallback = (grid, dataCell) => { gridCol.styleCallback = (grid, dataCell) => {
const dc = dataCell as { const dc = dataCell as {
itemIndex?: number; itemIndex?: number;
@@ -338,19 +360,21 @@ const TabDataGrid = ({
return mergeCellStyleCallbackResult(baseStyleName, undefined); return mergeCellStyleCallbackResult(baseStyleName, undefined);
} }
const row = data[itemIndex] as Record<string, string | number>; const row = data[itemIndex] as Record<string, string | number>;
const extra = cellStyleCallbackRef.current?.({ const extra = hasUserCellStyleCallback
rowIndex: itemIndex, ? cellStyleCallbackRef.current?.({
fieldName, rowIndex: itemIndex,
row, fieldName,
grid: grid as CellStyleCallbackParams["grid"], row,
}); grid: grid as CellStyleCallbackParams["grid"],
})
: undefined;
return mergeCellStyleCallbackResult(baseStyleName, extra); return mergeCellStyleCallbackResult(baseStyleName, extra);
}; };
attachedCount += 1; attachedCount += 1;
} }
if (attachedCount === 0 && attempt < 20) { if (attachedCount === 0 && attempt < 20) {
setTimeout(() => attachCellStyleCallbacksForTab(tabKey, columns, attempt + 1), 100); setTimeout(() => attachCellStyleCallbacksForTab(tabKey, columns, columnLayout, fixedCols, attempt + 1), 100);
return; return;
} }
@@ -459,6 +483,14 @@ const TabDataGrid = ({
const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep
? prepareGridColumnsForExcelExport(gridView, columns) ? prepareGridColumnsForExcelExport(gridView, columns)
: undefined; : undefined;
const savedFixedColCount = columnInfoItem.fixedColumns
? releaseFixedOptionsForExcelExport(
gridView,
columnInfoItem.fixedColumns,
)
: undefined;
disableAlternateRowStyle(gridView, true);
const exportBaseName = buildExcelExportFileBaseName( const exportBaseName = buildExcelExportFileBaseName(
menuName, menuName,
@@ -473,6 +505,12 @@ const TabDataGrid = ({
footer: 'hidden', footer: 'hidden',
allColumns: !useExcelColumnVisibilityPrep, allColumns: !useExcelColumnVisibilityPrep,
done: () => { done: () => {
if (savedFixedColCount !== undefined) {
restoreFixedOptionsAfterExcelExport(
gridView,
savedFixedColCount,
);
}
if (excelExportVisibilitySnapshot) { if (excelExportVisibilitySnapshot) {
restoreGridColumnsAfterExcelExport(gridView, excelExportVisibilitySnapshot); restoreGridColumnsAfterExcelExport(gridView, excelExportVisibilitySnapshot);
} }
@@ -968,6 +1006,11 @@ const TabDataGrid = ({
return null; return null;
} }
const { columns, columnLayout } = info; const { columns, columnLayout } = info;
const fixedColumnNameSet = buildFixedColumnNameSet(
columns,
columnLayout,
info.fixedColumns,
);
const currentRowData = rowDataMap.get(tabKey) || []; const currentRowData = rowDataMap.get(tabKey) || [];
return ( return (
@@ -1074,6 +1117,7 @@ const TabDataGrid = ({
gridView.setOptions({ gridView.setOptions({
summaryMode: "aggregate" summaryMode: "aggregate"
}); });
disableAlternateRowStyle(gridView);
// footerUserSpans가 있는 경우 setColumnLayout으로 설정 // footerUserSpans가 있는 경우 setColumnLayout으로 설정
const checkFooterUserSpans = (layout: unknown[]): boolean => { const checkFooterUserSpans = (layout: unknown[]): boolean => {
@@ -1189,9 +1233,27 @@ const TabDataGrid = ({
if (currentInfo.fixedColumns !== undefined && currentInfo.fixedColumns > 0) { if (currentInfo.fixedColumns !== undefined && currentInfo.fixedColumns > 0) {
gridView.setFixedOptions({ gridView.setFixedOptions({
colCount: currentInfo.fixedColumns, 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 초기화 // 각 탭별 ref 초기화
if (!sortDirRefs.current.has(tabKey)) { if (!sortDirRefs.current.has(tabKey)) {
sortDirRefs.current.set(tabKey, { current: {} }); sortDirRefs.current.set(tabKey, { current: {} });
@@ -1247,9 +1309,17 @@ const TabDataGrid = ({
const onSortingHandler = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef, findGridElement); const onSortingHandler = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef, findGridElement);
(gridView as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = enableSort ? onSortingHandler : () => false; (gridView as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = enableSort ? onSortingHandler : () => false;
if (cellStyleCallbackRef.current) { if (
cellStyleCallbackRef.current ||
tabFixedSet.size > 0
) {
setTimeout(() => { setTimeout(() => {
attachCellStyleCallbacksForTab(tabKey, columns); attachCellStyleCallbacksForTab(
tabKey,
columns,
currentInfo.columnLayout,
currentInfo.fixedColumns,
);
}, 100); }, 100);
} }
}, 150); }, 150);
@@ -1262,7 +1332,16 @@ const TabDataGrid = ({
layout={columnLayout} layout={columnLayout}
rows={currentRowData} rows={currentRowData}
> >
{columns.map((column) => ( {columns.map((column) => {
const resolvedStyleName =
resolveColumnStyleName(
column,
fixedColumnNameSet,
) ||
(column.editable === false
? "rg-cell-readonly"
: undefined);
return (
<RGDataColumn <RGDataColumn
key={column.name} key={column.name}
name={column.name} name={column.name}
@@ -1271,11 +1350,9 @@ const TabDataGrid = ({
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.00' })} {...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.00' })}
header={column.header} header={column.header}
editable={column.editable} editable={column.editable}
{...(column.styleName != null {...(resolvedStyleName != null
? { styleName: column.styleName } ? { styleName: resolvedStyleName }
: column.editable === false : {})}
? { styleName: 'rg-cell-readonly' as const }
: {})}
{...(column.visible === false && { visible: false })} {...(column.visible === false && { visible: false })}
footer={column.footer && !Array.isArray(column.footer) ? { footer={column.footer && !Array.isArray(column.footer) ? {
text: (column.footer as { text?: string }).text, 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} mergeRule={column.mergeCells ? { criteria: column.mergeGroupField ? `values['${column.mergeGroupField}'] + value` : 'value' } : undefined}
sortable={enableSort && !column.sortdisable} sortable={enableSort && !column.sortdisable}
/> />
))} );
})}
</RealGridReact> </RealGridReact>
) : null} ) : null}
</div> </div>
@@ -10,7 +10,12 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import type { RealGridReact } from "realgrid-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 { import {
Button, Button,
Dialog, Dialog,
@@ -21,17 +26,17 @@ import {
DialogTitle, DialogTitle,
LoadingWithAnimation, LoadingWithAnimation,
} from "@common/components"; } from "@common/components";
import { getUserInfo } from "../../../../../packages/common/components/src/api";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import { cellContentTooltip } from "@/components/module/verificationResultCellTooltip";
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination"; import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
import type { import type {
CellEditedCallbackParams, CellEditedCallbackParams,
CellStyleCallbackParams, CellStyleCallbackParams,
ColumnInfo, ColumnInfo,
} from "@/components/type/DataGrid.type"; } 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>; 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> = { const VRFC_MTHD_NAME_MAP: Record<VerificationMethodCd, string> = {
NV: "일반", NV: "일반",
IMR: "I-MR", IMR: "시계열",
HB: "HB", HB: "모집단",
}; };
export type VerificationExceptionSubmitItem = { export type VerificationExceptionSubmitItem = {
@@ -63,7 +68,7 @@ function setRowCheckByItemIndex(
try { try {
gv?.checkItem?.(itemIndex, checked, false); gv?.checkItem?.(itemIndex, checked, false);
} catch { } 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: "vrfcMthdCd", fieldName: "vrfcMthdCd", header: { text: "검증유형코드" }, editable: false, visible: false },
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false }, { name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
{ name: "admdstNm", fieldName: "admdstNm", 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: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false }, { name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false }, { name: "nowCn", fieldName: "nowCn", header: { text: "입력값" }, editable: false },
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false }, { name: "cmprCn", fieldName: "cmprCn", header: { text: "기준값" }, editable: false },
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "처리여부" }, editable: false }, { name: "vrfcSttsNm", fieldName: "vrfcSttsNm", header: { text: "처리여부" }, editable: false },
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false }, { name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false }, { name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
{ {
@@ -111,11 +117,12 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
"vrfcMthdNm", "vrfcMthdNm",
"ctpvNm", "ctpvNm",
"admdstNm", "admdstNm",
"fcltNm",
"vrfcTrgtNm", "vrfcTrgtNm",
"rsnExpln", "rsnExpln",
"nowVlCn", "nowCn",
"compVlCn", "cmprCn",
"prcsYn", "vrfcSttsNm",
"regDt", "regDt",
"mdfcnDt", "mdfcnDt",
"prcsOpnnExpln", "prcsOpnnExpln",
@@ -125,7 +132,7 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
export interface VerificationResultDialogQueryContext { export interface VerificationResultDialogQueryContext {
sewerBzmnCd: string; sewerBzmnCd: string;
ctpvNm: string; ctpvNm: string;
sggNm: string; sggNm?: string;
statsYr: string; statsYr: string;
statsCd: string; statsCd: string;
} }
@@ -201,11 +208,12 @@ export function VerificationResultDialog({
vrfcMthdNm: VRFC_MTHD_NAME_MAP[vrfcMthdCd] ?? item.vrfcMthdNm, vrfcMthdNm: VRFC_MTHD_NAME_MAP[vrfcMthdCd] ?? item.vrfcMthdNm,
ctpvNm: item.ctpvNm, ctpvNm: item.ctpvNm,
admdstNm: item.admdstNm ?? item.sggNm ?? "-", admdstNm: item.admdstNm ?? item.sggNm ?? "-",
fcltNm: item.fcltNm ?? "-",
vrfcTrgtNm: item.vrfcTrgtNm, vrfcTrgtNm: item.vrfcTrgtNm,
rsnExpln: item.rsnExpln, rsnExpln: item.rsnExpln,
nowVlCn: item.nowVlCn ?? "-", nowCn: item.nowCn ?? "-",
compVlCn: item.compVlCn ?? "-", cmprCn: item.cmprCn ?? "-",
prcsYn: item.prcsYnNm ?? PRCS_YN_LABEL[item.prcsYn] ?? item.prcsYn, vrfcSttsNm: item.vrfcSttsNm ?? PRCS_YN_LABEL[item.vrfcSttsCd] ?? item.vrfcSttsCd,
regDt: (item.regDt ?? "").slice(0, 16), regDt: (item.regDt ?? "").slice(0, 16),
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16), mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
prcsOpnnExpln: item.prcsOpnnExpln ?? "", prcsOpnnExpln: item.prcsOpnnExpln ?? "",
@@ -343,7 +351,7 @@ export function VerificationResultDialog({
sewerBzmnCd: stableQueryContext.sewerBzmnCd, sewerBzmnCd: stableQueryContext.sewerBzmnCd,
statsYr: stableQueryContext.statsYr, statsYr: stableQueryContext.statsYr,
statsCd: stableQueryContext.statsCd, statsCd: stableQueryContext.statsCd,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
exceptionList: payload, exceptionList: payload,
}); });
if (response.status === "success") { if (response.status === "success") {
@@ -413,6 +421,7 @@ export function VerificationResultDialog({
getGridDataRef={getGridDataRef} getGridDataRef={getGridDataRef}
onCellEdited={onPrcsOpnnExplnEdited} onCellEdited={onPrcsOpnnExplnEdited}
cellStyleCallback={verificationMthdCellStyle} cellStyleCallback={verificationMthdCellStyle}
cellTooltipCallback={cellContentTooltip}
onItemChecked={(itemIndex, checked) => { onItemChecked={(itemIndex, checked) => {
setCheckedIndices((prev) => { setCheckedIndices((prev) => {
const next = new Set(prev); const next = new Set(prev);
@@ -10,9 +10,10 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import type { RealGridReact } from "realgrid-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 { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface"; import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
import { import {
@@ -30,18 +31,20 @@ import {
TabsTrigger, TabsTrigger,
} from "@common/components"; } from "@common/components";
import { getUserInfo } from "../../../../../packages/common/components/src/api";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import { cellContentTooltip } from "@/components/module/verificationResultCellTooltip";
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination"; import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type"; import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
import { useAlertStore } from "@/shared/lib";
type VerificationResultGridRow = Record<string, string | number>; type VerificationResultGridRow = Record<string, string | number>;
type VerificationHistoryTabKey = "NV" | "IMR" | "HB"; type VerificationHistoryTabKey = "NV" | "IMR" | "HB";
const verificationHistoryTabs: { value: VerificationHistoryTabKey; label: string }[] = [ const verificationHistoryTabs: { value: VerificationHistoryTabKey; label: string }[] = [
{ value: "NV", label: "일반검증 내역" }, { value: "NV", label: "일반검증 내역" },
{ value: "IMR", label: "I-MR 검증내역" }, { value: "IMR", label: "시계열 검증내역" },
{ value: "HB", label: "HB 검증내역" }, { value: "HB", label: "모집단 검증내역" },
]; ];
const PAGE_SIZE = 30; const PAGE_SIZE = 30;
@@ -88,7 +91,7 @@ function uncheckGridRow(gridRef: MutableRefObject<RealGridReact | null>, itemInd
try { try {
(gridRef.current?.gridView as GridViewWithCheckApi | undefined)?.checkItem?.(itemIndex, false, false); (gridRef.current?.gridView as GridViewWithCheckApi | undefined)?.checkItem?.(itemIndex, false, false);
} catch { } catch {
/* ignore */ console.error('RealGrid 체크 API error');
} }
} }
@@ -110,7 +113,7 @@ function syncCheckedMapFromGrid(
if (!entry) continue; if (!entry) continue;
const sn = Number(entry.vrfcSn); const sn = Number(entry.vrfcSn);
if (!Number.isFinite(sn)) continue; if (!Number.isFinite(sn)) continue;
next.set(sn, String(entry.prcsYn ?? "")); next.set(sn, String(entry.vrfcSttsCd ?? ""));
} }
return next; return next;
} }
@@ -121,15 +124,15 @@ function selectionHasNonExceptionResetAllowed(
): boolean { ): boolean {
for (const [vrfcSn, storedPrcsYn] of map) { for (const [vrfcSn, storedPrcsYn] of map) {
const onPage = currentEntries.find((e) => Number(e.vrfcSn) === vrfcSn); 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; if (!canExceptionReset(prcsYn)) return true;
} }
return false; return false;
} }
function verificationPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined { function verificationPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined {
if (fieldName !== "prcsYnNm") return undefined; if (fieldName !== "vrfcSttsNm") return undefined;
const code = prcsYnCodeToNumber(row.prcsYn); const code = prcsYnCodeToNumber(row.vrfcSttsCd);
if (code === 0 || code === 3) return "rg-cell-vrf-prcs-bad"; if (code === 0 || code === 3) return "rg-cell-vrf-prcs-bad";
return "rg-cell-vrf-prcs-ok"; return "rg-cell-vrf-prcs-ok";
} }
@@ -138,33 +141,35 @@ const verificationResultColumnInfo: ColumnInfo = {
columns: [ columns: [
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false }, { name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
{ name: "admdstNm", fieldName: "admdstNm", 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: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false }, { name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false }, { name: "nowCn", fieldName: "nowCn", header: { text: "입력값" }, editable: false },
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false }, { name: "cmprCn", fieldName: "cmprCn", header: { text: "기준값" }, editable: false },
{ name: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false }, { name: "vrfcSttsNm", fieldName: "vrfcSttsNm", header: { text: "처리여부" }, editable: false },
{ name: "stVrfRjtComNm", fieldName: "stVrfRjtComNm", header: { text: "반려상태" }, editable: false }, { name: "statsVrfcRjtComNm", fieldName: "statsVrfcRjtComNm", header: { text: "반려상태" }, editable: false },
{ name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false }, { name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false },
{ name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false }, { name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false },
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false }, { name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
{ name: "mdfcnDt", fieldName: "mdfcnDt", 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 }, { name: "vrfcSn", fieldName: "vrfcSn", header: { text: "검증일련번호" }, editable: false, visible: false },
], ],
columnLayout: [ columnLayout: [
"ctpvNm", "ctpvNm",
"admdstNm", "admdstNm",
"fcltNm",
"vrfcTrgtNm", "vrfcTrgtNm",
"rsnExpln", "rsnExpln",
"nowVlCn", "nowCn",
"compVlCn", "cmprCn",
"prcsYnNm", "vrfcSttsNm",
"stVrfRjtComNm", "statsVrfcRjtComNm",
"prcsOpnnExpln", "prcsOpnnExpln",
"rjctOpnnCn", "rjctOpnnCn",
"regDt", "regDt",
"mdfcnDt", "mdfcnDt",
"prcsYn", "vrfcSttsCd",
"vrfcSn", "vrfcSn",
], ],
}; };
@@ -175,7 +180,7 @@ export interface VerificationResultHistoryDialogProps {
queryContext?: { queryContext?: {
sewerBzmnCd: string; sewerBzmnCd: string;
ctpvNm: string; ctpvNm: string;
sggNm: string; sggNm?: string;
statsYr: string; statsYr: string;
statsCd: string; statsCd: string;
} | null; } | null;
@@ -375,17 +380,18 @@ export function VerificationResultHistoryDialog({
return { return {
vrfcSn: item.vrfcSn, vrfcSn: item.vrfcSn,
__checked: isChecked ? 1 : 0, __checked: isChecked ? 1 : 0,
ctpvNm: item.ctpvNm, ctpvNm: item.ctpvNm ?? "-",
admdstNm: item.admdstNm ?? item.sggNm ?? "-", admdstNm: item.admdstNm ?? item.sggNm ?? "-",
fcltNm: item.fcltNm ?? "-",
vrfcTrgtNm: item.vrfcTrgtNm, vrfcTrgtNm: item.vrfcTrgtNm,
rsnExpln: item.rsnExpln, rsnExpln: item.rsnExpln,
nowVlCn: item.nowVlCn ?? "-", nowCn: item.nowCn ?? "-",
compVlCn: item.compVlCn ?? "-", cmprCn: item.cmprCn ?? "-",
prcsYnNm: item.prcsYnNm ?? "-", vrfcSttsNm: item.vrfcSttsNm ?? "-",
stVrfRjtComNm: item.stVrfRjtComNm ?? "-", statsVrfcRjtComNm: item.statsVrfcRjtComNm ?? "-",
prcsOpnnExpln: item.prcsOpnnExpln ?? "-", prcsOpnnExpln: item.prcsOpnnExpln ?? "-",
rjctOpnnCn: item.rjctOpnnCn ?? "-", rjctOpnnCn: item.rjctOpnnCn ?? "-",
prcsYn: item.prcsYn ?? "", vrfcSttsCd: item.vrfcSttsCd ?? "",
regDt: (item.regDt ?? "").slice(0, 16), regDt: (item.regDt ?? "").slice(0, 16),
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16), mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
}; };
@@ -428,10 +434,10 @@ export function VerificationResultHistoryDialog({
} }
setCheckedVrfcByTab((prev) => ({ ...prev, [tab]: syncedMap })); setCheckedVrfcByTab((prev) => ({ ...prev, [tab]: syncedMap }));
const vrfcSnList = Array.from(syncedMap.keys()); const vrfcSnList = Array.from(syncedMap.keys());
let mdfrId: string; let mdfrUuid: string;
try { try {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
mdfrId = userInfo.userUnqId; mdfrUuid = userInfo.userUnqId;
} catch { } catch {
alert("사용자 정보를 불러오지 못했습니다."); alert("사용자 정보를 불러오지 못했습니다.");
return; return;
@@ -443,7 +449,7 @@ export function VerificationResultHistoryDialog({
statsYr: stableQueryContext.statsYr, statsYr: stableQueryContext.statsYr,
sewerBzmnCd: stableQueryContext.sewerBzmnCd, sewerBzmnCd: stableQueryContext.sewerBzmnCd,
statsCd: stableQueryContext.statsCd, statsCd: stableQueryContext.statsCd,
mdfrId, mdfrUuid,
vrfcSnList, vrfcSnList,
}); });
if (response.status !== "success") { if (response.status !== "success") {
@@ -500,18 +506,19 @@ export function VerificationResultHistoryDialog({
enableSort={false} enableSort={false}
getGridRef={gridRef} getGridRef={gridRef}
cellStyleCallback={verificationPrcsYnNmCellStyle} cellStyleCallback={verificationPrcsYnNmCellStyle}
cellTooltipCallback={cellContentTooltip}
onItemChecked={(itemIndex, checked) => { onItemChecked={(itemIndex, checked) => {
const entry = entries[itemIndex]; const entry = entries[itemIndex];
if (!entry) return; if (!entry) return;
const vrfcSn = Number(entry.vrfcSn); const vrfcSn = Number(entry.vrfcSn);
if (!Number.isFinite(vrfcSn)) return; if (!Number.isFinite(vrfcSn)) return;
if (checked && !canExceptionReset(entry.prcsYn)) { if (checked && !canExceptionReset(entry.vrfcSttsCd)) {
alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE); alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE);
uncheckGridRow(gridRef, itemIndex); uncheckGridRow(gridRef, itemIndex);
return; return;
} }
updateCheckedVrfcForTab(tab, (map) => { updateCheckedVrfcForTab(tab, (map) => {
if (checked) map.set(vrfcSn, String(entry.prcsYn ?? "")); if (checked) map.set(vrfcSn, String(entry.vrfcSttsCd ?? ""));
else map.delete(vrfcSn); else map.delete(vrfcSn);
}); });
}} }}
@@ -524,14 +531,14 @@ export function VerificationResultHistoryDialog({
map.delete(vrfcSn); map.delete(vrfcSn);
return; return;
} }
if (canExceptionReset(entry.prcsYn)) { if (canExceptionReset(entry.vrfcSttsCd)) {
map.set(vrfcSn, String(entry.prcsYn ?? "")); map.set(vrfcSn, String(entry.vrfcSttsCd ?? ""));
} else { } else {
uncheckGridRow(gridRef, itemIndex); 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); alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE);
} }
}} }}
@@ -558,7 +565,7 @@ export function VerificationResultHistoryDialog({
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[80%]" aria-describedby={undefined}> <DialogContent className="max-w-[80%]" aria-describedby={undefined}>
<DialogHeader> <DialogHeader>
<DialogTitle>{`${title} - ${queryContext?.statsYr} ${queryContext?.ctpvNm} ${queryContext?.sggNm}`}</DialogTitle> <DialogTitle>{`${title} - ${queryContext?.statsYr} ${queryContext?.ctpvNm} ${queryContext?.sggNm ? queryContext?.sggNm : ''}`}</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="max-h-[85vh] space-y-5 overflow-auto"> <div className="max-h-[85vh] space-y-5 overflow-auto">
{errorMessage && ( {errorMessage && (
@@ -6,6 +6,7 @@ import {
DoubleArrowLeftIcon, DoubleArrowLeftIcon,
DoubleArrowRightIcon, DoubleArrowRightIcon,
} from "@radix-ui/react-icons"; } from "@radix-ui/react-icons";
import { Button, cn } from "@common/components"; import { Button, cn } from "@common/components";
export type VerificationResultHistoryPaginationProps = { 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"; "use client";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useTabStore as useGridTabStore } from "@rsone/components";
import { import {
type InfiniteData, type InfiniteData,
keepPreviousData, keepPreviousData,
useInfiniteQuery, useInfiniteQuery,
useQueryClient, useQueryClient,
} from "@tanstack/react-query"; } 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 { verificationInputReportApi } from "@/entities/statistics/api/verification-input-report-service";
import type { import type {
VerificationInputReportItem, VerificationInputReportItem,
VerificationInputReportResponse, VerificationInputReportResponse,
VerificationInputReportTypeCountingInfo, VerificationInputReportTypeCountingInfo,
} from "@/entities/statistics/model/verification-input-report-interface"; } 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 { import {
Badge, Badge,
type BadgeProps, type BadgeProps,
Label,
LoadingWithAnimation, LoadingWithAnimation,
Tabs, Tabs,
TabsContent, TabsContent,
TabsList, TabsList,
TabsTrigger, TabsTrigger,
Label,
} from "@common/components"; } 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"; type VerificationReportTabKey = "NV" | "IMR" | "HB";
const VERIFICATION_REPORT_TABS: { value: VerificationReportTabKey; label: string }[] = [ const VERIFICATION_REPORT_TABS: { value: VerificationReportTabKey; label: string }[] = [
{ value: "NV", label: "일반" }, { value: "NV", label: "일반" },
{ value: "IMR", label: "I-MR" }, { value: "IMR", label: "시계열" },
{ value: "HB", label: "HB" }, { value: "HB", label: "모집단" },
]; ];
const TYPE_CODE_COLOR_MAP: Record<string, BadgeProps["color"]> = { 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> = { const TYPE_CODE_NAME_MAP: Record<string, string> = {
NV: "일반", NV: "일반",
IMR: "I-MR", IMR: "시계열",
HB: "HB", HB: "모집단",
}; };
const PAGE_SIZE = 100; const PAGE_SIZE = 100;
@@ -378,8 +379,8 @@ const VerificationSelectionReport = ({
<div className="flex justify-between text-lg text-muted-foreground"> <div className="flex justify-between text-lg text-muted-foreground">
<span>{formatReportItemRegion(item)}</span> <span>{formatReportItemRegion(item)}</span>
<span> <span>
현재: {item.nowVlCn ?? "-"} / 비교:{" "} 현재: {item.nowCn ?? "-"} / 비교:{" "}
{item.compVlCn ?? "-"} {item.cmprCn ?? "-"}
</span> </span>
</div> </div>
</div> </div>
@@ -1,20 +1,56 @@
import { import {
type RefObject,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useState, useState,
type RefObject,
} from "react"; } from "react";
import { flushSync } from "react-dom"; import { flushSync } from "react-dom";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { ValueType } from "realgrid"; import { ValueType } from "realgrid";
import type { RealGridReact } from "realgrid-react"; 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 { LoadingWithAnimation } from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import SubmitDailog from "./SubmitDailog";
import AddArticleButton from "@/components/button/AddArticleButton"; import AddArticleButton from "@/components/button/AddArticleButton";
import CancelSubmitButton from "@/components/button/CancelSubmitButton"; import CancelSubmitButton from "@/components/button/CancelSubmitButton";
import DeleteArticleButton from "@/components/button/DeleteArticleButton"; import DeleteArticleButton from "@/components/button/DeleteArticleButton";
@@ -24,49 +60,24 @@ import SaveButton from "@/components/button/SaveButton";
import SubmitButton from "@/components/button/SumitButton"; import SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton"; import VerificationResultButton from "@/components/button/VerificationResultButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results"; import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import SubmitDailog from "./SubmitDailog";
import { ExcretaTreatFactInpColumn } from "@/components/util/column/input/ExcretaTreatFactInpColumn";
import type { import type {
CellStyleCallbackParams, CellStyleCallbackParams,
CellTooltipCallbackParams, CellTooltipCallbackParams,
ColumnConfig, ColumnConfig,
} from "@/components/type/DataGrid.type"; } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.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 { toGridRows } from "@/components/util/grid/gridFunction";
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat"; import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
import { statisticsInputExcretaTreatmentFacilityApi } from "@/entities/statistics/api/input/statistics-input-excreta-treatment-facility-service"; import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
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 { useAdminStore } from "@/store/admin-store"; import { useAdminStore } from "@/store/admin-store";
import { useTabStore } from "@/store/tab-store";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
type GridRowWithVerification = Record<string, string | number> & { type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: ExcretaTreatmentFacilityVerificationResult; verificationResult?: ExcretaTreatmentFacilityVerificationResult;
@@ -82,8 +93,8 @@ const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -91,10 +102,22 @@ const computeTabEnterable = (
return periodEnterable; return periodEnterable;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || "") return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== "ALL") }
: true;
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( function computeTabToolbarSnapshot(
@@ -107,8 +130,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = const showSubmittedInInputPeriodToolbar =
isSubmitted && periodEnterable && gugunOk; isSubmitted && periodEnterable && gugunOk;
@@ -128,9 +151,8 @@ function buildExcretaTreatmentFacilitySearchParam(
const fcltNm = values.fctName?.trim(); const fcltNm = values.fctName?.trim();
return { return {
statsYr: values.year, statsYr: values.year,
sewerBzmnCd: values.bsnmCd,
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
...(fcltNm ? { fcltNm } : {}), ...(fcltNm ? { fcltNm } : {}),
...(overrides?.mode ? { mode: overrides.mode } : {}), ...(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 { function toGridNumber(value: string | number | null | undefined): number {
const n = Number(value); const n = Number(value);
return Number.isFinite(n) ? n : 0; return Number.isFinite(n) ? n : 0;
@@ -210,9 +225,9 @@ function toApiYmd(value: string | number | Date | null | undefined): string | nu
function resolveExcretaTreatmentFacilityAdmdst( function resolveExcretaTreatmentFacilityAdmdst(
row: Record<string, string | number>, row: Record<string, string | number>,
sggOptions: Sgg[], sggOptions: Sgg[],
): { admdstCd: string; admdstNm: string } { ): { admdstCd: string | null; admdstNm: string | null } {
const existingCd = String(row.admdstCd ?? "").trim(); const existingCd = nullableTrimmedString(row.admdstCd);
const existingNm = String(row.admdstNm ?? "").trim(); const existingNm = nullableTrimmedString(row.admdstNm);
if (existingCd && existingNm) { if (existingCd && existingNm) {
return { admdstCd: existingCd, admdstNm: existingNm }; return { admdstCd: existingCd, admdstNm: existingNm };
} }
@@ -221,7 +236,10 @@ function resolveExcretaTreatmentFacilityAdmdst(
if (matched) { if (matched) {
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun }; return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
} }
return { admdstCd: existingCd, admdstNm: existingNm || sggNm }; return {
admdstCd: existingCd,
admdstNm: existingNm ?? nullableTrimmedString(sggNm),
};
} }
/** 그리드 행 → ST00070001 저장/삭제 API 항목 */ /** 그리드 행 → ST00070001 저장/삭제 API 항목 */
@@ -236,38 +254,38 @@ function buildExcretaTreatmentFacilitySaveItem(
return { return {
admdstCd, admdstCd,
admdstNm, admdstNm,
fcltSn: String(row.fcltSn ?? "").trim(), fcltSn: nullableTrimmedString(row.fcltSn),
fcltNm: String(row.fcltNm ?? ""), fcltNm: nullableTrimmedString(row.fcltNm),
fcltPstnCn: String(row.fcltPstnCn ?? ""), fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
telNo: String(row.telNo ?? ""), telnoCn: nullableTrimmedString(row.telnoCn),
cmptnStrtYmd: toApiYmd(row.cmptnStrtYmd), cmptnStrtYmd: toApiYmd(row.cmptnStrtYmd),
oprtngStrtYmd: toApiYmd(row.oprtngStrtYmd), oprtngStrtYmd: toApiYmd(row.oprtngStrtYmd),
wholPjtcoAmt: toGridNumber(row.wholPjtcoAmt), wholPjtcoAmt: toGridNumber(row.wholPjtcoAmt),
prcsFcltCpct: toGridNumber(row.prcsFcltCpct), fcltCpct: toGridNumber(row.fcltCpct),
cpct: toGridNumber(row.cpct), excrFcltPrcqty: toGridNumber(row.excrFcltPrcqty),
oprtngDayCnt: toGridNumber(row.oprtngDayCnt), fcltOprtngDayCnt: toGridNumber(row.fcltOprtngDayCnt),
prcsMthdExpln: String(row.prcsMthdExpln ?? ""), prcsExpln: nullableTrimmedString(row.prcsExpln),
linkTmplntCn: String(row.linkTmplntCn ?? ""), linkTmplntCn: nullableTrimmedString(row.linkTmplntCn),
linkPrcsQnt: toGridNumber(row.linkPrcsQnt), linkPrcsQnt: toGridNumber(row.linkPrcsQnt),
inflwBodWqVl: toGridNumber(row.inflwBodWqVl), inflwWqBod: toGridNumber(row.inflwWqBod),
inflwTocWqVl: toGridNumber(row.inflwTocWqVl), inflwWqTorgcb: toGridNumber(row.inflwWqTorgcb),
inflwSsWqVl: toGridNumber(row.inflwSsWqVl), inflwWqSpsld: toGridNumber(row.inflwWqSpsld),
inflwTnWqVl: toGridNumber(row.inflwTnWqVl), inflwWqDtn: toGridNumber(row.inflwWqDtn),
inflwTpWqVl: toGridNumber(row.inflwTpWqVl), inflwWqTph: toGridNumber(row.inflwWqTph),
inflwClfgCnt: toGridNumber(row.inflwClfgCnt), fcltInflwClfgCnt: toGridNumber(row.fcltInflwClfgCnt),
dschgBodWqVl: toGridNumber(row.dschgBodWqVl), dschgWqBod: toGridNumber(row.dschgWqBod),
dschgTocWqVl: toGridNumber(row.dschgTocWqVl), dschgWqTorgcb: toGridNumber(row.dschgWqTorgcb),
dschgSsWqVl: toGridNumber(row.dschgSsWqVl), dschgWqSpsld: toGridNumber(row.dschgWqSpsld),
dschgTnWqVl: toGridNumber(row.dschgTnWqVl), dschgWqDtn: toGridNumber(row.dschgWqDtn),
dschgTpWqVl: toGridNumber(row.dschgTpWqVl), dschgWqTph: toGridNumber(row.dschgWqTph),
dschgClfgCnt: toGridNumber(row.dschgClfgCnt), fcltDschgClfgCnt: toGridNumber(row.fcltDschgClfgCnt),
wtsysNm: String(row.wtsysNm ?? ""), fcltWtsysNm: nullableTrimmedString(row.fcltWtsysNm),
trbtrNm: String(row.trbtrNm ?? ""), fcltTrbtrNm: nullableTrimmedString(row.fcltTrbtrNm),
msirbCn: String(row.msirbCn ?? ""), msirbCn: nullableTrimmedString(row.msirbCn),
ssirbCn: String(row.ssirbCn ?? ""), ssirbCn: nullableTrimmedString(row.ssirbCn),
operMthdCdCn: String(row.operMthdCdCn ?? ""), operCn: nullableTrimmedString(row.operCn),
wholNope: toGridNumber(row.wholNope), wholNope: toGridNumber(row.wholNope),
sftyAcdntEvntcsCnt: toGridNumber(row.sftyAcdntEvntcsCnt), sftyAcdntNocs: toGridNumber(row.sftyAcdntNocs),
rmrk: nullableTrimmedString(row.rmrk), rmrk: nullableTrimmedString(row.rmrk),
}; };
} }
@@ -290,11 +308,11 @@ async function waitForImrJobCompleted(
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
function createEmptyExcretaTreatmentFacilityRow( function createEmptyExcretaTreatmentFacilityRow(
@@ -316,6 +334,8 @@ const EXCRETA_TREATMENT_FACILITY_NAME_GUIDE_MESSAGE =
// 하수도 통계 관리 > 07. 분뇨처리시설 // 하수도 통계 관리 > 07. 분뇨처리시설
const ExcretaTreatmentFacilityInp = () => { const ExcretaTreatmentFacilityInp = () => {
const { sidoList } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
@@ -338,6 +358,8 @@ const ExcretaTreatmentFacilityInp = () => {
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null); const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
const [statsCd, setStatsCd] = useState(""); const [statsCd, setStatsCd] = useState("");
const [isVerificationFailed, setIsVerificationFailed] = useState(false); const [isVerificationFailed, setIsVerificationFailed] = useState(false);
const [footerVerificationResult, setFooterVerificationResult] =
useState<VerificationResultLikeMap | null>(null);
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [sggOptions, setSggOptions] = useState<Sgg[]>([]); const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
@@ -356,7 +378,10 @@ const ExcretaTreatmentFacilityInp = () => {
const sggOptionsSidoRef = useRef<string | null>(null); const sggOptionsSidoRef = useRef<string | null>(null);
const [searchLoaded, setSearchLoaded] = useState(false); const [searchLoaded, setSearchLoaded] = useState(false);
const activeTabId = useTabStore((s) => s.activeTabId);
useEffect(() => { useEffect(() => {
if (activeTabId !== STATISTICS_INPUT_URL_PATH.EXCRETA_TREATMENT_FACILITY) return;
let cancelled = false; let cancelled = false;
void (async () => { void (async () => {
try { try {
@@ -382,7 +407,7 @@ const ExcretaTreatmentFacilityInp = () => {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, [activeTabId]);
const loadSggOptions = useCallback(async (sido: string): Promise<Sgg[]> => { const loadSggOptions = useCallback(async (sido: string): Promise<Sgg[]> => {
if (!sido || sido === "ALL") { if (!sido || sido === "ALL") {
@@ -402,7 +427,7 @@ const ExcretaTreatmentFacilityInp = () => {
const columnInfo = useMemo(() => { const columnInfo = useMemo(() => {
const enterable = const enterable =
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo; (computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
const rawColumns = ExcretaTreatFactInpColumn.columns ?? []; const rawColumns = ExcretaTreatFactInpColumn.columns ?? [];
const base = enterable const base = enterable
? rawColumns ? rawColumns
@@ -430,7 +455,7 @@ const ExcretaTreatmentFacilityInp = () => {
}, [statusInfo, inputPeriodEnterable, isAdminInfo, sggOptions, columnGuidesByEngCol]); }, [statusInfo, inputPeriodEnterable, isAdminInfo, sggOptions, columnGuidesByEngCol]);
const canEditGrid = const canEditGrid =
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo; (computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
const getCurrentRows = (): Record<string, string | number>[] => const getCurrentRows = (): Record<string, string | number>[] =>
gridGetRowsRef.current?.() ?? rowData; gridGetRowsRef.current?.() ?? rowData;
@@ -466,13 +491,18 @@ const ExcretaTreatmentFacilityInp = () => {
statusInfo: nextStatusInfo, statusInfo: nextStatusInfo,
excretaTreatmentFacilityList, excretaTreatmentFacilityList,
} = response.data; } = response.data;
const list = excretaTreatmentFacilityList ?? []; const list = filterBySelectedAdmdstCd(
excretaTreatmentFacilityList ?? [],
sidoListRef.current,
values,
);
const gridRows = mapExcretaTreatmentFacilityListToGridRows(list, commonInfo.ctpvNm); const gridRows = mapExcretaTreatmentFacilityListToGridRows(list, commonInfo.ctpvNm);
setRowData(gridRows); setRowData(gridRows);
setStatusInfo(nextStatusInfo); setStatusInfo(nextStatusInfo);
setStatsCd(commonInfo.statsCd); setStatsCd(commonInfo.statsCd);
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo)); setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
setFooterVerificationResult(commonInfo.verificationResult ?? null);
if (overrides?.mode !== "PRE") { if (overrides?.mode !== "PRE") {
setIsNotApplicable(gridRows.length === 0); setIsNotApplicable(gridRows.length === 0);
} else if (gridRows.length > 0) { } else if (gridRows.length > 0) {
@@ -532,6 +562,7 @@ const ExcretaTreatmentFacilityInp = () => {
setStatusInfo(null); setStatusInfo(null);
setStatsCd(""); setStatsCd("");
setIsVerificationFailed(false); setIsVerificationFailed(false);
setFooterVerificationResult(null);
setIsNotApplicable(false); setIsNotApplicable(false);
setInputPeriodEnterable(true); setInputPeriodEnterable(true);
setSggOptions([]); setSggOptions([]);
@@ -581,14 +612,10 @@ const ExcretaTreatmentFacilityInp = () => {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD, statsCd: statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD,
}; };
@@ -622,7 +649,7 @@ const ExcretaTreatmentFacilityInp = () => {
if (!(await confirm("저장 하시겠습니까?"))) return; if (!(await confirm("저장 하시겠습니까?"))) return;
commitGrid(); commitGrid();
const noApplicable = isNotApplicable; const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
const currentData = getCurrentRows(); const currentData = getCurrentRows();
const saveList = noApplicable const saveList = noApplicable
? [] ? []
@@ -633,7 +660,7 @@ const ExcretaTreatmentFacilityInp = () => {
.filter((row) => String(row.fcltSn ?? "").trim() !== "") .filter((row) => String(row.fcltSn ?? "").trim() !== "")
.map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions)); .map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions));
if (!noApplicable && saveList.length === 0) { if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -646,12 +673,8 @@ const ExcretaTreatmentFacilityInp = () => {
{ {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL" userUuid: userInfo.userUnqId,
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
userId: userInfo.userUnqId,
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
noApplicable, noApplicable,
saveList, saveList,
@@ -686,9 +709,9 @@ const ExcretaTreatmentFacilityInp = () => {
return; return;
} }
const noApplicable = isNotApplicable; const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
const currentData = getCurrentRows(); const currentData = getCurrentRows();
if (!noApplicable && currentData.length === 0) { if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -713,7 +736,7 @@ const ExcretaTreatmentFacilityInp = () => {
} }
commitGrid(); commitGrid();
const noApplicable = isNotApplicable; const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
const currentData = getCurrentRows(); const currentData = getCurrentRows();
const submitList = noApplicable const submitList = noApplicable
? [] ? []
@@ -724,19 +747,15 @@ const ExcretaTreatmentFacilityInp = () => {
.filter((row) => String(row.fcltSn ?? "").trim() !== "") .filter((row) => String(row.fcltSn ?? "").trim() !== "")
.map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions)); .map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions));
if (!noApplicable && submitList.length === 0) { if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
const statsCdForSubmit = statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD; const statsCdForSubmit = statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD;
const verificationQuery = { const verificationQuery = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
statsCd: statsCdForSubmit, statsCd: statsCdForSubmit,
}; };
@@ -755,20 +774,16 @@ const ExcretaTreatmentFacilityInp = () => {
{ {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL" userUuid: userInfo.userUnqId,
? "" lclgvPicNm: submitInfo.lclgvPicNm,
: (lastSearchedValues.gugun ?? ""), lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
userId: userInfo.userUnqId, lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laePicNm: submitInfo.laePicNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
noApplicable, noApplicable,
submitList, submitList,
@@ -786,15 +801,16 @@ const ExcretaTreatmentFacilityInp = () => {
if (finalStatus === "5") { if (finalStatus === "5") {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중..."); setGlobalProgressLabel("시계열 검증 진행 중...");
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
stats_cd: statsCdForSubmit, stats_cd: statsCdForSubmit,
stats_yr: lastSearchedValues.year ?? "", stats_yr: lastSearchedValues.year ?? "",
lae_sbmsn_stts_cd: lae_sbmsn_stts_cd:
statusInfo?.laeSbmsnSttsCd === "4" ? "4" : "2", statusInfo?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
gen_verify_stts_cd: nvpassed ? "1" : "2", gen_verify_stts_cd: nvpassed ? "1" : "2",
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -941,19 +957,19 @@ const ExcretaTreatmentFacilityInp = () => {
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" e.vrfcCd === "IMR"
? "IMR검증 - " ? "시계열 검증 - "
: e.vrfcCd === "NV" : e.vrfcCd === "NV"
? "일반검증 - " ? "일반검증 - "
: "HB검증 -"; : "모집단 검증 -";
return `${prefix}${e.rsnExpln} return `${prefix}${e.rsnExpln}
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; (검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
} }
}; };
const searchContextForToolbar = lastSearchedValues ?? searchValues; const searchContextForToolbar = lastSearchedValues;
const toolbarSnapshot = computeTabToolbarSnapshot( const toolbarSnapshot = computeTabToolbarSnapshot(
statusInfo, statusInfo,
rowData.length > 0, rowData.length > 0,
@@ -973,23 +989,23 @@ const ExcretaTreatmentFacilityInp = () => {
hasSearched && hasSearched &&
toolbarGroupAllowed && toolbarGroupAllowed &&
(showSaveInToolbar || showEnterableToolbarButtons); (showSaveInToolbar || showEnterableToolbarButtons);
const tabEnterableForGrid =
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const showNotApplicable = const showNotApplicable =
searchLoaded && searchLoaded &&
hasSearched && hasSearched &&
rowData.length === 0 && rowData.length === 0 &&
computeTabEnterable(statusInfo, inputPeriodEnterable); computeTabEnterable(statusInfo, inputPeriodEnterable) &&
submitOk;
const tabEnterableForGrid =
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
const verificationQueryContext = (() => { const verificationQueryContext = (() => {
const ctx = lastSearchedValues ?? searchValues; const ctx = lastSearchedValues ?? searchValues;
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -1005,6 +1021,7 @@ const ExcretaTreatmentFacilityInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
fctName={true} fctName={true}
@@ -1013,7 +1030,7 @@ const ExcretaTreatmentFacilityInp = () => {
onValuesChange={setSearchValues} onValuesChange={setSearchValues}
/> />
{showActionToolbarCard && ( {showActionToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]"> <div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
<VerificationResultButton <VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog} VerificationResultButtonFunc={openVerificationHistoryDialog}
@@ -1024,18 +1041,18 @@ const ExcretaTreatmentFacilityInp = () => {
<LastYearDataButton <LastYearDataButton
getLastYearDataFunc={() => void getLastYearDataFunc()} getLastYearDataFunc={() => void getLastYearDataFunc()}
/> />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{statusInfo?.laeSbmsnSttsCd === "2" && ( {statusInfo?.lclgvSbmsnSttsCd === "2" && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div> </div>
</div> </div>
)} )}
{showArticleToolbarCard && ( {showArticleToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]"> <div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
{showNotApplicable && ( {showNotApplicable && (
<NotApplicableCheckButton <NotApplicableCheckButton
@@ -1043,10 +1060,10 @@ const ExcretaTreatmentFacilityInp = () => {
setIsCheck={setIsNotApplicable} setIsCheck={setIsNotApplicable}
/> />
)} )}
<AddArticleButton addArticleFunc={addArticleFunc} /> <AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} /> <DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</div> </div>
</div> </div>
)} )}
{isLoading && ( {isLoading && (
<div className="absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10 [data-theme='black']:bg-transparent"> <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>
</div> </div>
)} )}
<DataGrid <DataGrid
key={canEditGrid ? "enterable" : "readonly"} key={canEditGrid ? "enterable" : "readonly"}
getGridRef={gridRef as RefObject<RealGridReact | null>} getGridRef={gridRef as RefObject<RealGridReact | null>}
columnInfo={columnInfo} columnInfo={columnInfo}
rowStartNum={ExcretaTreatFactInpColumn.rowStartNum} rowStartNum={ExcretaTreatFactInpColumn.rowStartNum}
fixedColumns={ExcretaTreatFactInpColumn.fixedColumns} fixedColumns={ExcretaTreatFactInpColumn.fixedColumns}
rowData={rowData} rowData={rowData}
setRowData={setRowData} setRowData={setRowData}
getGridDataRef={gridGetRowsRef} getGridDataRef={gridGetRowsRef}
showHeaderCheckbox showHeaderCheckbox
statusInfo={statusInfo} statusInfo={statusInfo}
@@ -1072,6 +1089,7 @@ const ExcretaTreatmentFacilityInp = () => {
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd} excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
cellStyleCallback={verificationCellStyle} cellStyleCallback={verificationCellStyle}
footerVerificationResult={footerVerificationResult}
cellTooltipCallback={verificationCellTooltip} cellTooltipCallback={verificationCellTooltip}
/> />
</div> </div>
@@ -1,8 +1,9 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { CalendarIcon } from "lucide-react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { CalendarIcon } from "lucide-react";
import { useAlertStore } from "@/shared/lib"; import { useAlertStore } from "@/shared/lib";
import { import {
@@ -248,7 +249,7 @@ export function MainFacilityMappingSaveDialog({
setIsSubmitting(true); setIsSubmitting(true);
await onConfirm?.({ await onConfirm?.({
prxyBzentyNmCn: prxyBzentyNmCn.trim(), prxyBzentyNmCn: prxyBzentyNmCn.trim(),
cnsgnCstVl, cnsgnCstVl: cnsgnCstVl,
wholNope, wholNope,
prxyCtrtCn: prxyCtrtCnParam, prxyCtrtCn: prxyCtrtCnParam,
}); });
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; 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 { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; 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 { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { import type {
PrivateSewageDesigStatusSaveItem,
PrivateSewageFacilityVerificationResult,
PrivateSewageDesigStatusItem, PrivateSewageDesigStatusItem,
PrivateSewageDesigStatusSaveItem,
PrivateSewageDesigStatusSaveRequest, PrivateSewageDesigStatusSaveRequest,
PrivateSewageDesigStatusSubmitRequest, PrivateSewageDesigStatusSubmitRequest,
PrivateSewageFacilityVerificationResult,
SepticTankStatusItem,
SepticTankStatusSaveItem, SepticTankStatusSaveItem,
SepticTankStatusSaveRequest, SepticTankStatusSaveRequest,
SepticTankStatusSubmitRequest, SepticTankStatusSubmitRequest,
SewageTreatFactStatusItem, SewageTreatFactStatusItem,
SewageTreatFactStatusRequestParams,
SewageTreatFactStatusSaveItem, SewageTreatFactStatusSaveItem,
SewageTreatFactStatusSaveRequest, SewageTreatFactStatusSaveRequest,
SewageTreatFactStatusSubmitRequest, SewageTreatFactStatusSubmitRequest,
SepticTankStatusItem,
SewageTreatFactStatusRequestParams,
} from "@/entities/statistics/model/input/statistics-input-private-sewage-facility-interface"; } 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 { 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 { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
@@ -31,7 +31,15 @@ import type {
cancelSubmitRequest, cancelSubmitRequest,
ImrStatusResponse, ImrStatusResponse,
SewerageStatusInfo, SewerageStatusInfo,
Sido,
} from "@/entities/statistics/model/statistics-interface"; } from "@/entities/statistics/model/statistics-interface";
import {
filterBySelectedAdmdstCd,
isGugunOkForToolbar,
isSubmitOkForToolbar,
toInputSearchRegion,
} from "@/entities/statistics";
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api"; 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 SubmitDailog from "./SubmitDailog";
import CancelSubmitButton from "@/components/button/CancelSubmitButton"; 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 LastYearDataButton from "@/components/button/LastYearDataButton";
import SaveButton from "@/components/button/SaveButton";
import SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton"; import VerificationResultButton from "@/components/button/VerificationResultButton";
import DataGrid from "@/components/module/DataGrid"; 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 { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type"; import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.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 { PrivateSewageDesigStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/PrivateSewageDesigStatusInpColumn";
import { SepticTankStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SepticTankStatusInpColumn"; import { SepticTankStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SepticTankStatusInpColumn";
import { SewageTreatFactStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SewageTreatFactStatusInpColumn"; import { SewageTreatFactStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SewageTreatFactStatusInpColumn";
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
import { toGridRows } from "@/components/util/grid/gridFunction"; 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 { useAdminStore } from "@/store/admin-store";
import { useTabStore } from "@/store/tab-store";
const PrivateSewageFacilityInpTabList = [ const PrivateSewageFacilityInpTabList = [
SewageTreatFactStatusInpColumn, SewageTreatFactStatusInpColumn,
@@ -74,10 +88,6 @@ const PRIVATE_SEWAGE_FACILITY_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY: Record<string, s
[PrivateSewageDesigStatusInpColumn.tabKey]: "ST00080003", [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> & { type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: PrivateSewageFacilityVerificationResult; verificationResult?: PrivateSewageFacilityVerificationResult;
}; };
@@ -99,9 +109,8 @@ function buildPrivateSewageFacilitySearchParam(
): SewageTreatFactStatusRequestParams { ): SewageTreatFactStatusRequestParams {
return { return {
statsYr: values.year, statsYr: values.year,
sewerBzmnCd: values.bsnmCd,
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
...(overrides?.mode ? { mode: overrides.mode } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}),
}; };
} }
@@ -110,8 +119,8 @@ const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -119,10 +128,14 @@ const computeTabEnterable = (
return periodEnterable; return periodEnterable;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || "") return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== "ALL") }
: true;
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
return isSubmitOkForToolbar(sidoListRef.current, values);
} }
function computeTabToolbarSnapshot( function computeTabToolbarSnapshot(
@@ -135,8 +148,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk; const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
const showActionToolbarCard = const showActionToolbarCard =
@@ -158,8 +171,8 @@ function tabRowDataExists(
function mapRegionalListToGridRows< function mapRegionalListToGridRows<
T extends { verificationResult?: PrivateSewageFacilityVerificationResult }, T extends { verificationResult?: PrivateSewageFacilityVerificationResult },
>(list: T[], ctpvNm: string): GridRowWithVerification[] { >(list: T[], ctpvNm: string, values?: SearchFormValues | null): GridRowWithVerification[] {
return list.map((item) => { return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
const { verificationResult, ...rest } = item; const { verificationResult, ...rest } = item;
const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification;
if (verificationResult) row.verificationResult = verificationResult; 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( function buildColumnFormatNumber(
columns: { fieldName?: string; dataFormat?: string }[], columns: { fieldName?: string; dataFormat?: string }[],
) { ) {
@@ -206,23 +208,23 @@ function buildSewageTreatFactStatusSaveList(
const formatNumber = buildColumnFormatNumber(SewageTreatFactStatusInpColumn.columns); const formatNumber = buildColumnFormatNumber(SewageTreatFactStatusInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
step1StaaPrcsQnt: formatNumber(d.step1StaaPrcsQnt, "step1StaaPrcsQnt"), frlvlStaaPrcqty: formatNumber(d.frlvlStaaPrcqty, "frlvlStaaPrcqty"),
step2StaaPrcsQnt: formatNumber(d.step2StaaPrcsQnt, "step2StaaPrcsQnt"), sclvlStaaPrcqty: formatNumber(d.sclvlStaaPrcqty, "sclvlStaaPrcqty"),
step3StaaPrcsQnt: formatNumber(d.step3StaaPrcsQnt, "step3StaaPrcsQnt"), thlvlStaaPrcqty: formatNumber(d.thlvlStaaPrcqty, "thlvlStaaPrcqty"),
step4StaaPrcsQnt: formatNumber(d.step4StaaPrcsQnt, "step4StaaPrcsQnt"), folvlStaaPrcqty: formatNumber(d.folvlStaaPrcqty, "folvlStaaPrcqty"),
step5StaaPrcsQnt: formatNumber(d.step5StaaPrcsQnt, "step5StaaPrcsQnt"), filvlStaaPrcqty: formatNumber(d.filvlStaaPrcqty, "filvlStaaPrcqty"),
step6StaaPrcsQnt: formatNumber(d.step6StaaPrcsQnt, "step6StaaPrcsQnt"), sxlvlStaaPrcqty: formatNumber(d.sxlvlStaaPrcqty, "sxlvlStaaPrcqty"),
step7StaaPrcsQnt: formatNumber(d.step7StaaPrcsQnt, "step7StaaPrcsQnt"), svlvlStaaPrcqty: formatNumber(d.svlvlStaaPrcqty, "svlvlStaaPrcqty"),
step1StaaExcpPrcsQnt: formatNumber(d.step1StaaExcpPrcsQnt, "step1StaaExcpPrcsQnt"), frlvlStaaOtsdPrcqty: formatNumber(d.frlvlStaaOtsdPrcqty, "frlvlStaaOtsdPrcqty"),
step2StaaExcpPrcsQnt: formatNumber(d.step2StaaExcpPrcsQnt, "step2StaaExcpPrcsQnt"), sclvlStaaOtsdPrcqty: formatNumber(d.sclvlStaaOtsdPrcqty, "sclvlStaaOtsdPrcqty"),
step3StaaExcpPrcsQnt: formatNumber(d.step3StaaExcpPrcsQnt, "step3StaaExcpPrcsQnt"), thlvlStaaOtsdPrcqty: formatNumber(d.thlvlStaaOtsdPrcqty, "thlvlStaaOtsdPrcqty"),
step4StaaExcpPrcsQnt: formatNumber(d.step4StaaExcpPrcsQnt, "step4StaaExcpPrcsQnt"), folvlStaaOtsdPrcqty: formatNumber(d.folvlStaaOtsdPrcqty, "folvlStaaOtsdPrcqty"),
step5StaaExcpPrcsQnt: formatNumber(d.step5StaaExcpPrcsQnt, "step5StaaExcpPrcsQnt"), filvlStaaOtsdPrcqty: formatNumber(d.filvlStaaOtsdPrcqty, "filvlStaaOtsdPrcqty"),
step6StaaExcpPrcsQnt: formatNumber(d.step6StaaExcpPrcsQnt, "step6StaaExcpPrcsQnt"), sxlvlStaaOtsdPrcqty: formatNumber(d.sxlvlStaaOtsdPrcqty, "sxlvlStaaOtsdPrcqty"),
step7StaaExcpPrcsQnt: formatNumber(d.step7StaaExcpPrcsQnt, "step7StaaExcpPrcsQnt"), svlvlStaaOtsdPrcqty: formatNumber(d.svlvlStaaOtsdPrcqty, "svlvlStaaOtsdPrcqty"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
})); }));
} }
@@ -232,37 +234,37 @@ function buildSepticTankStatusSaveList(
const formatNumber = buildColumnFormatNumber(SepticTankStatusInpColumn.columns); const formatNumber = buildColumnFormatNumber(SepticTankStatusInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
staaInsdSttkNprsflCnt: formatNumber(d.staaInsdSttkNprsflCnt, "staaInsdSttkNprsflCnt"), staaInsdSpctnkNatrCnt: formatNumber(d.staaInsdSpctnkNatrCnt, "staaInsdSpctnkNatrCnt"),
step1StaaInsdSttkFrcCnt: formatNumber(d.step1StaaInsdSttkFrcCnt, "step1StaaInsdSttkFrcCnt"), staaInsdSpctnkFrcCnt: formatNumber(d.staaInsdSpctnkFrcCnt, "staaInsdSpctnkFrcCnt"),
staaInsdSprktpFltrtbCnt: formatNumber(d.staaInsdSprktpFltrtbCnt, "staaInsdSprktpFltrtbCnt"), staaInsdSprklFiltrCnt: formatNumber(d.staaInsdSprklFiltrCnt, "staaInsdSprklFiltrCnt"),
staaInsdAertMthdCnt: formatNumber(d.staaInsdAertMthdCnt, "staaInsdAertMthdCnt"), staaInsdAirCnt: formatNumber(d.staaInsdAirCnt, "staaInsdAirCnt"),
staaInsdCntcAertCnt: formatNumber(d.staaInsdCntcAertCnt, "staaInsdCntcAertCnt"), staaInsdCntcAirCnt: formatNumber(d.staaInsdCntcAirCnt, "staaInsdCntcAirCnt"),
staaInsdEtcCnt: formatNumber(d.staaInsdEtcCnt, "staaInsdEtcCnt"), staaInsdEtcCnt: formatNumber(d.staaInsdEtcCnt, "staaInsdEtcCnt"),
step1StaaInsdNope: formatNumber(d.step1StaaInsdNope, "step1StaaInsdNope"), frlvlStaaInsdNope: formatNumber(d.frlvlStaaInsdNope, "frlvlStaaInsdNope"),
step2StaaInsdNope: formatNumber(d.step2StaaInsdNope, "step2StaaInsdNope"), sclvlStaaInsdNope: formatNumber(d.sclvlStaaInsdNope, "sclvlStaaInsdNope"),
step3StaaInsdNope: formatNumber(d.step3StaaInsdNope, "step3StaaInsdNope"), thlvlStaaInsdNope: formatNumber(d.thlvlStaaInsdNope, "thlvlStaaInsdNope"),
step4StaaInsdNope: formatNumber(d.step4StaaInsdNope, "step4StaaInsdNope"), folvlStaaInsdNope: formatNumber(d.folvlStaaInsdNope, "folvlStaaInsdNope"),
step5StaaInsdNope: formatNumber(d.step5StaaInsdNope, "step5StaaInsdNope"), filvlStaaInsdNope: formatNumber(d.filvlStaaInsdNope, "filvlStaaInsdNope"),
step6StaaInsdNope: formatNumber(d.step6StaaInsdNope, "step6StaaInsdNope"), sxlvlStaaInsdNope: formatNumber(d.sxlvlStaaInsdNope, "sxlvlStaaInsdNope"),
step7StaaInsdNope: formatNumber(d.step7StaaInsdNope, "step7StaaInsdNope"), svlvlStaaInsdNope: formatNumber(d.svlvlStaaInsdNope, "svlvlStaaInsdNope"),
step8StaaInsdNope: formatNumber(d.step8StaaInsdNope, "step8StaaInsdNope"), eilvlStaaInsdNope: formatNumber(d.eilvlStaaInsdNope, "eilvlStaaInsdNope"),
staaExcpSttkNprsflCnt: formatNumber(d.staaExcpSttkNprsflCnt, "staaExcpSttkNprsflCnt"), staaOtsdSpctnkNatrCnt: formatNumber(d.staaOtsdSpctnkNatrCnt, "staaOtsdSpctnkNatrCnt"),
step1StaaExcpSttkFrcCnt: formatNumber(d.step1StaaExcpSttkFrcCnt, "step1StaaExcpSttkFrcCnt"), staaOtsdSpctnkFrcCnt: formatNumber(d.staaOtsdSpctnkFrcCnt, "staaOtsdSpctnkFrcCnt"),
staaExcpSprktpFltrtbCnt: formatNumber(d.staaExcpSprktpFltrtbCnt, "staaExcpSprktpFltrtbCnt"), staaOtsdSprklFiltrCnt: formatNumber(d.staaOtsdSprklFiltrCnt, "staaOtsdSprklFiltrCnt"),
staaExcpAertMthdCnt: formatNumber(d.staaExcpAertMthdCnt, "staaExcpAertMthdCnt"), staaOtsdAirCnt: formatNumber(d.staaOtsdAirCnt, "staaOtsdAirCnt"),
staaExcpCntcAertCnt: formatNumber(d.staaExcpCntcAertCnt, "staaExcpCntcAertCnt"), staaOtsdCntcAirCnt: formatNumber(d.staaOtsdCntcAirCnt, "staaOtsdCntcAirCnt"),
staaExcpEtcCnt: formatNumber(d.staaExcpEtcCnt, "staaExcpEtcCnt"), staaOtsdEtcCnt: formatNumber(d.staaOtsdEtcCnt, "staaOtsdEtcCnt"),
step1StaaExcpNope: formatNumber(d.step1StaaExcpNope, "step1StaaExcpNope"), frlvlStaaOtsdNope: formatNumber(d.frlvlStaaOtsdNope, "frlvlStaaOtsdNope"),
step2StaaExcpNope: formatNumber(d.step2StaaExcpNope, "step2StaaExcpNope"), sclvlStaaOtsdNope: formatNumber(d.sclvlStaaOtsdNope, "sclvlStaaOtsdNope"),
step3StaaExcpNope: formatNumber(d.step3StaaExcpNope, "step3StaaExcpNope"), thlvlStaaOtsdNope: formatNumber(d.thlvlStaaOtsdNope, "thlvlStaaOtsdNope"),
step4StaaExcpNope: formatNumber(d.step4StaaExcpNope, "step4StaaExcpNope"), folvlStaaOtsdNope: formatNumber(d.folvlStaaOtsdNope, "folvlStaaOtsdNope"),
step5StaaExcpNope: formatNumber(d.step5StaaExcpNope, "step5StaaExcpNope"), filvlStaaOtsdNope: formatNumber(d.filvlStaaOtsdNope, "filvlStaaOtsdNope"),
step6StaaExcpNope: formatNumber(d.step6StaaExcpNope, "step6StaaExcpNope"), sxlvlStaaOtsdNope: formatNumber(d.sxlvlStaaOtsdNope, "sxlvlStaaOtsdNope"),
step7StaaExcpNope: formatNumber(d.step7StaaExcpNope, "step7StaaExcpNope"), svlvlStaaOtsdNope: formatNumber(d.svlvlStaaOtsdNope, "svlvlStaaOtsdNope"),
step8StaaExcpNope: formatNumber(d.step8StaaExcpNope, "step8StaaExcpNope"), eilvlStaaOtsdNope: formatNumber(d.eilvlStaaOtsdNope, "eilvlStaaOtsdNope"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
})); }));
} }
@@ -272,18 +274,18 @@ function buildPrivateSewageDesigStatusSaveList(
const formatNumber = buildColumnFormatNumber(PrivateSewageDesigStatusInpColumn.columns); const formatNumber = buildColumnFormatNumber(PrivateSewageDesigStatusInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
indvSwagePbancRgnCnt: formatNumber(d.indvSwagePbancRgnCnt, "indvSwagePbancRgnCnt"), indvSwagePbancRgnCnt: formatNumber(d.indvSwagePbancRgnCnt, "indvSwagePbancRgnCnt"),
indvSwageDsgnScpLen: formatNumber(d.indvSwageDsgnScpLen, "indvSwageDsgnScpLen"), indvSwageDsgnScpLen: formatNumber(d.indvSwageDsgnScpLen, "indvSwageDsgnScpLen"),
spctnkGrssFcltCnt: formatNumber(d.spctnkGrssFcltCnt, "spctnkGrssFcltCnt"), spctnkWholFcltCnt: formatNumber(d.spctnkWholFcltCnt, "spctnkWholFcltCnt"),
spctnkQnt: formatNumber(d.spctnkQnt, "spctnkQnt"), spctnkPrcqty: formatNumber(d.spctnkPrcqty, "spctnkPrcqty"),
drtwPrcsFcltGrssFcltCnt: formatNumber( swgdplFcltWholFcltCnt: formatNumber(
d.drtwPrcsFcltGrssFcltCnt, d.swgdplFcltWholFcltCnt,
"drtwPrcsFcltGrssFcltCnt", "swgdplFcltWholFcltCnt",
), ),
drtwPrcsFcltQnt: formatNumber(d.drtwPrcsFcltQnt, "drtwPrcsFcltQnt"), swgdplFcltPrcqty: formatNumber(d.swgdplFcltPrcqty, "swgdplFcltPrcqty"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
})); }));
} }
@@ -305,11 +307,11 @@ async function waitForImrJobCompleted(
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
function resolvePrivateSewageFacilityTab(tabKey: string) { function resolvePrivateSewageFacilityTab(tabKey: string) {
@@ -322,6 +324,8 @@ function resolvePrivateSewageFacilityTab(tabKey: string) {
// 하수도 통계 관리 > 08. 개인하수처리시설 // 하수도 통계 관리 > 08. 개인하수처리시설
const PrivateSewageFacilityInp = () => { const PrivateSewageFacilityInp = () => {
const { sidoList } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
@@ -345,9 +349,15 @@ const PrivateSewageFacilityInp = () => {
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
Map<string, boolean> Map<string, boolean>
>(new Map()); >(new Map());
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< const [footerVerificationByTab, setFooterVerificationByTab] = useState<
Map<string, VerificationResultLikeMap | null>
>(new Map());
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
Map<string, TabToolbarSnapshot> Map<string, TabToolbarSnapshot>
>(new Map()); >(new Map());
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
() => new Map(),
);
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false); const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
const [showSubmitDialog, setShowSubmitDialog] = useState(false); const [showSubmitDialog, setShowSubmitDialog] = useState(false);
@@ -362,42 +372,30 @@ const PrivateSewageFacilityInp = () => {
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null); const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
activeTabRef.current = activeTab; activeTabRef.current = activeTab;
const activeTabId = useTabStore((s) => s.activeTabId);
useEffect(() => { 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; let cancelled = false;
void (async () => { void (async () => {
const entries = await Promise.all( const next = await fetchColumnGuidesRecord(statsCd);
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;
}
}),
);
if (cancelled) return; if (cancelled) return;
const guidesByStatsCd = Object.fromEntries(entries); setColumnGuidesByTab((prev) =>
setColumnGuidesByTab( Object.prototype.hasOwnProperty.call(prev, tabKey)
Object.fromEntries( ? prev
Object.entries(PRIVATE_SEWAGE_FACILITY_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY).map( : { ...prev, [tabKey]: next },
([tabKey, statsCd]) => [tabKey, guidesByStatsCd[statsCd] ?? {}],
),
),
); );
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, [activeTabId, activeTab, columnGuidesByTab]);
const commitToolbarSnapshotForLoadedTab = ( const commitToolbarSnapshotForLoadedTab = (
tabKey: string, tabKey: string,
@@ -420,6 +418,13 @@ const PrivateSewageFacilityInp = () => {
); );
return next; return next;
}); });
if (searchContext) {
setSearchContextByTab((prev) => {
const next = new Map(prev);
next.set(tabKey, searchContext);
return next;
});
}
}; };
const applyRowMap = ( const applyRowMap = (
@@ -462,6 +467,7 @@ const PrivateSewageFacilityInp = () => {
const gridRows = mapRegionalListToGridRows( const gridRows = mapRegionalListToGridRows(
(sewageTreatFactStatusList ?? []) as SewageTreatFactStatusItem[], (sewageTreatFactStatusList ?? []) as SewageTreatFactStatusItem[],
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -472,6 +478,9 @@ const PrivateSewageFacilityInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
return true; return true;
@@ -490,6 +499,7 @@ const PrivateSewageFacilityInp = () => {
const gridRows = mapRegionalListToGridRows( const gridRows = mapRegionalListToGridRows(
(septicTankStatusList ?? []) as SepticTankStatusItem[], (septicTankStatusList ?? []) as SepticTankStatusItem[],
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -500,6 +510,9 @@ const PrivateSewageFacilityInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
return true; return true;
@@ -518,6 +531,7 @@ const PrivateSewageFacilityInp = () => {
const gridRows = mapRegionalListToGridRows( const gridRows = mapRegionalListToGridRows(
(privateSewageDesigStatusList ?? []) as PrivateSewageDesigStatusItem[], (privateSewageDesigStatusList ?? []) as PrivateSewageDesigStatusItem[],
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -528,6 +542,9 @@ const PrivateSewageFacilityInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
return true; return true;
@@ -545,7 +562,9 @@ const PrivateSewageFacilityInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setLastSearchedValues(null); setLastSearchedValues(null);
@@ -587,7 +606,9 @@ const PrivateSewageFacilityInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
} }
@@ -697,14 +718,10 @@ const PrivateSewageFacilityInp = () => {
} }
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -785,28 +802,20 @@ const PrivateSewageFacilityInp = () => {
const submitInfoBase = { const submitInfoBase = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL" lclgvPicNm: submitInfo.lclgvPicNm,
? "" lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
: (lastSearchedValues.gugun ?? ""), lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laePicNm: submitInfo.laePicNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
}; };
const verificationQuery = { const verificationQuery = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -826,12 +835,12 @@ const PrivateSewageFacilityInp = () => {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
setGlobalProgressLabel("일반 검증 진행 중..."); 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) { if (isSewageTreatTab) {
const param: SewageTreatFactStatusSubmitRequest = { const param: SewageTreatFactStatusSubmitRequest = {
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: buildSewageTreatFactStatusSaveList(currentData), submitList: buildSewageTreatFactStatusSaveList(currentData),
...adminOpt, ...adminOpt,
}; };
@@ -842,7 +851,7 @@ const PrivateSewageFacilityInp = () => {
} else if (isSepticTankTab) { } else if (isSepticTankTab) {
const param: SepticTankStatusSubmitRequest = { const param: SepticTankStatusSubmitRequest = {
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: buildSepticTankStatusSaveList(currentData), submitList: buildSepticTankStatusSaveList(currentData),
...adminOpt, ...adminOpt,
}; };
@@ -851,7 +860,7 @@ const PrivateSewageFacilityInp = () => {
} else { } else {
const param: PrivateSewageDesigStatusSubmitRequest = { const param: PrivateSewageDesigStatusSubmitRequest = {
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: buildPrivateSewageDesigStatusSaveList(currentData), submitList: buildPrivateSewageDesigStatusSaveList(currentData),
...adminOpt, ...adminOpt,
}; };
@@ -871,14 +880,15 @@ const PrivateSewageFacilityInp = () => {
if (finalStatus === "5") { if (finalStatus === "5") {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중..."); setGlobalProgressLabel("시계열 검증 진행 중...");
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
stats_cd: statsCdForTab, stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? "", 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", gen_verify_stts_cd: nvpassed ? "1" : "2",
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -962,11 +972,7 @@ const PrivateSewageFacilityInp = () => {
const baseRequest = { const baseRequest = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
}; };
try { try {
@@ -976,7 +982,7 @@ const PrivateSewageFacilityInp = () => {
if (isSewageTreatTab) { if (isSewageTreatTab) {
const param: SewageTreatFactStatusSaveRequest = { const param: SewageTreatFactStatusSaveRequest = {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: buildSewageTreatFactStatusSaveList(currentData), saveList: buildSewageTreatFactStatusSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
}; };
@@ -1002,7 +1008,7 @@ const PrivateSewageFacilityInp = () => {
if (isSepticTankTab) { if (isSepticTankTab) {
const param: SepticTankStatusSaveRequest = { const param: SepticTankStatusSaveRequest = {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: buildSepticTankStatusSaveList(currentData), saveList: buildSepticTankStatusSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
}; };
@@ -1025,7 +1031,7 @@ const PrivateSewageFacilityInp = () => {
const param: PrivateSewageDesigStatusSaveRequest = { const param: PrivateSewageDesigStatusSaveRequest = {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: buildPrivateSewageDesigStatusSaveList(currentData), saveList: buildPrivateSewageDesigStatusSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
}; };
@@ -1068,9 +1074,8 @@ const PrivateSewageFacilityInp = () => {
const statsCd = tabStatsCdMap.get(activeTab); const statsCd = tabStatsCdMap.get(activeTab);
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -1081,10 +1086,10 @@ const PrivateSewageFacilityInp = () => {
for (const tab of PrivateSewageFacilityInpTabList) { for (const tab of PrivateSewageFacilityInpTabList) {
const tabKey = tab.tabKey; const tabKey = tab.tabKey;
const canEdit = const canEdit =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const rawColumns = (tab.columns ?? []) as ColumnConfig[]; const rawColumns = (tab.columns ?? []) as ColumnConfig[];
const base = canEdit const base = canEdit
? rawColumns ? rawColumns
@@ -1101,19 +1106,20 @@ const PrivateSewageFacilityInp = () => {
return map; return map;
}, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]); }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]);
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const activeTabToolbar = const activeTabToolbar =
toolbarSnapshotByTab.get(activeTab) ??
computeTabToolbarSnapshot( computeTabToolbarSnapshot(
activeTabStatus, activeTabStatus,
tabRowDataExists(rowDataMap, activeTab), tabRowDataExists(rowDataMap, activeTab),
hasSearched, hasSearched,
lastSearchedValues, searchContextForToolbar,
inputPeriodEnterable, inputPeriodEnterable,
isAdminInfo, isAdminInfo,
); );
const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } = const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } =
activeTabToolbar; activeTabToolbar;
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const verificationCellStyle = ({ const verificationCellStyle = ({
fieldName, fieldName,
@@ -1146,11 +1152,11 @@ const PrivateSewageFacilityInp = () => {
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" e.vrfcCd === "IMR"
? "IMR검증 - " ? "시계열 검증 - "
: e.vrfcCd === "NV" : e.vrfcCd === "NV"
? "일반검증 - " ? "일반검증 - "
: "HB검증 - "; : "모집단 검증 - ";
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
@@ -1172,6 +1178,7 @@ const PrivateSewageFacilityInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
onSubmit={handleSearch} onSubmit={handleSearch}
@@ -1192,10 +1199,10 @@ const PrivateSewageFacilityInp = () => {
<LastYearDataButton <LastYearDataButton
getLastYearDataFunc={() => void lastYearDataFunc()} getLastYearDataFunc={() => void lastYearDataFunc()}
/> />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{activeTabStatus?.laeSbmsnSttsCd === "2" && ( {activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
@@ -1227,11 +1234,11 @@ const PrivateSewageFacilityInp = () => {
const tabKey = info.tabKey; const tabKey = info.tabKey;
const rowData = rowDataMap.get(tabKey) ?? []; const rowData = rowDataMap.get(tabKey) ?? [];
const tabEnterableForGrid = const tabEnterableForGrid =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const gugunOk = buildGugunOkForToolbar(lastSearchedValues); const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
return ( return (
@@ -1269,6 +1276,9 @@ const PrivateSewageFacilityInp = () => {
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
isExcelExport isExcelExport
cellStyleCallback={verificationCellStyle} cellStyleCallback={verificationCellStyle}
footerVerificationResult={
footerVerificationByTab.get(tabKey) ?? null
}
cellTooltipCallback={verificationCellTooltip} cellTooltipCallback={verificationCellTooltip}
getGridDataRef={ getGridDataRef={
activeTab === tabKey ? getGridDataRef : undefined 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 { 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 { STATISTICS_INPUT_URL_PATH } from '@/shared/lib/stats-cd-input-navigation';
import { useStatisticsInputPendingNavigation } from '@/shared/lib/use-statistics-input-pending-navigation'; import { useStatisticsInputPendingNavigation } from '@/shared/lib/use-statistics-input-pending-navigation';
import { statisticsInputSewSupplyRateApi } from '@/entities/statistics/api/input/statistics-input-sew-supply-rate-service'; import { statisticsInputSewSupplyRateApi } from '@/entities/statistics/api/input/statistics-input-sew-supply-rate-service';
@@ -23,7 +23,9 @@ import {
type SewerageSupplyRateVerificationResult, type SewerageSupplyRateVerificationResult,
} from '@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface'; } 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 { 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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from '@common/components';
import { getUserInfo } from '../../../../../../packages/common/components/src/api'; 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 UpdateOperateDataButton from '@/components/button/UpdateOperateDataButton';
import VerificationResultButton from '@/components/button/VerificationResultButton'; import VerificationResultButton from '@/components/button/VerificationResultButton';
import DataGrid from '@/components/module/DataGrid'; 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 SearchForm from '@/components/module/SearchForm';
import SplitDataGrid from '@/components/module/SplitDataGrid'; import SplitDataGrid from '@/components/module/SplitDataGrid';
import { openVerificationResultDialogIfNeeded } from '@/components/module/post-submit-verification-results';
import { VerificationResultDialog } from '@/components/module/VerificationResultDialog'; import { VerificationResultDialog } from '@/components/module/VerificationResultDialog';
import { VerificationResultHistoryDialog } from '@/components/module/VerificationResultHistoryDialog'; import { VerificationResultHistoryDialog } from '@/components/module/VerificationResultHistoryDialog';
import type { CellStyleCallbackParams, ColumnInfo, ColumnInfoList } from '@/components/type/DataGrid.type'; import type { CellStyleCallbackParams, ColumnInfo, ColumnInfoList } from '@/components/type/DataGrid.type';
import type { SearchFormValues } from '@/components/type/SearchForm.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 { PublicSewageFactInpColumnList } from '@/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactInpColumn';
import { fetchColumnGuidesRecord } from '@/components/util/grid/columnGuides';
import { toGridRows } from '@/components/util/grid/gridFunction'; 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 { useAdminStore } from '@/store/admin-store';
import { useTabStore } from '@/store/tab-store';
type GridRowWithVerification = Record<string, string | number> & { type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: SewerageSupplyRateVerificationResult; verificationResult?: SewerageSupplyRateVerificationResult;
@@ -62,18 +67,30 @@ type TabToolbarSnapshot = {
showActionToolbarCard: boolean; showActionToolbarCard: boolean;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || '') return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== 'ALL') }
: true;
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 = ( const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) { if (['2', '3'].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -91,8 +108,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === '2'; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === '2';
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk; const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
const showActionToolbarCard = const showActionToolbarCard =
@@ -122,11 +139,11 @@ async function waitForImrJobCompleted(
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); 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 === 'COMPLETED' && Number(res.job_prog) === 100) return;
if (res.status === 'FAILED' || res.status === 'ERROR' || res.status === 'CANCELLED') { 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); await sleep(pollIntervalMs);
} }
throw new Error('IMR 검증 완료 대기 시간이 초과되었습니다.'); throw new Error('시계열 검증 완료 대기 시간이 초과되었습니다.');
} }
type MainFacilityMappingSaveDraft = { type MainFacilityMappingSaveDraft = {
@@ -144,10 +161,6 @@ type MainFacilityMappingSaveDraft = {
initialPrxyCtrtCn: string; initialPrxyCtrtCn: string;
}; };
const PUBLIC_SEWAGE_FACILITY_COLUMN_GUIDE_TAB_KEYS = Array.from(
new Set(PublicSewageFactInpColumnList.map((tab) => tab.tabKey).filter(Boolean)),
);
/** 위탁계약기간: `yyyymmdd~yyyymmdd` (빈 값 허용). 하이픈 형식은 정규화 */ /** 위탁계약기간: `yyyymmdd~yyyymmdd` (빈 값 허용). 하이픈 형식은 정규화 */
function normalizePrxyCtrtCn(raw: unknown): string | null { function normalizePrxyCtrtCn(raw: unknown): string | null {
if (raw === null || raw === undefined) return null; if (raw === null || raw === undefined) return null;
@@ -160,6 +173,10 @@ function normalizePrxyCtrtCn(raw: unknown): string | null {
return s; return s;
} }
function isMainFacilityRow(row: Record<string, string | number>): boolean {
return String(row.isMainYn ?? '').toUpperCase() === 'Y';
}
function isValidPrxyCtrtCn(value: string | null): boolean { function isValidPrxyCtrtCn(value: string | null): boolean {
if (value === null) return true; if (value === null) return true;
if (!/^\d{8}~\d{8}$/.test(value)) return false; if (!/^\d{8}~\d{8}$/.test(value)) return false;
@@ -181,6 +198,8 @@ function validatePrxyCtrtCnInRows(rows: Record<string, string | number>[]): stri
// 하수도 통계 관리 > 06. 공공하수처리시설 // 하수도 통계 관리 > 06. 공공하수처리시설
const PublicSewageFacilityInp = () => { const PublicSewageFacilityInp = () => {
const { sidoList } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
@@ -203,9 +222,15 @@ const PublicSewageFacilityInp = () => {
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
Map<string, boolean> Map<string, boolean>
>(new Map()); >(new Map());
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
Map<string, VerificationResultLikeMap | null>
>(new Map());
/** 탭키별 `engColNm -> columnGuide` 캐시 */ /** 탭키별 `engColNm -> columnGuide` 캐시 */
const [columnGuidesByTab, setColumnGuidesByTab] = useState<Record<string, Record<string, string>>>({}); const [columnGuidesByTab, setColumnGuidesByTab] = useState<Record<string, Record<string, string>>>({});
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<Map<string, TabToolbarSnapshot>>(() => new Map()); const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<Map<string, TabToolbarSnapshot>>(() => new Map());
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
() => new Map(),
);
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]); const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
const [showDailog, setShowDailog] = useState<boolean>(false); const [showDailog, setShowDailog] = useState<boolean>(false);
@@ -244,10 +269,10 @@ const PublicSewageFacilityInp = () => {
alert('먼저 조회를 진행해주세요.'); alert('먼저 조회를 진행해주세요.');
return; return;
} }
const treatAsNoApplicable = getNoApplicableForRequest() || isNotApplicable; const treatAsNoApplicable = getNoApplicableForRequest();
const currentData = const currentData =
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? []; st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
if (!treatAsNoApplicable && (!currentData || currentData.length === 0)) { if (!treatAsNoApplicable && (!currentData || currentData.length === 0) && buildSubmitOkForToolbar(searchContextByTab.get(activeTab) ?? lastSearchedValues)) {
alert('해당사항 없음을 체크해주세요.'); alert('해당사항 없음을 체크해주세요.');
return; return;
} }
@@ -275,19 +300,13 @@ const PublicSewageFacilityInp = () => {
return Number.isFinite(num) ? num : 0; 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) => ({ return currentData.map((d) => ({
admdstCd: String(d.admdstCd ?? ''), admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: String(d.admdstNm ?? ''), admdstNm: nullableTrimmedString(d.admdstNm),
swtrmtFcltCd: String(d.swtrmtFcltCd ?? ''), swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
swtrmtFcltNm: String(d.swtrmtFcltNm ?? ''), swtrmtFcltNm: nullableTrimmedString(d.swtrmtFcltNm),
fcltSn: String(d.fcltSn ?? ''), fcltSn: nullableTrimmedString(d.fcltSn),
fcltPstnCn: d.fcltPstnCn === '' ? null : toNullableString(d.fcltPstnCn), fcltPstnCn: nullableTrimmedString(d.fcltPstnCn),
inflwSwagePhysQnt: toNumber(d.inflwSwagePhysQnt), inflwSwagePhysQnt: toNumber(d.inflwSwagePhysQnt),
inflwSwageBoilQnt: toNumber(d.inflwSwageBoilQnt), inflwSwageBoilQnt: toNumber(d.inflwSwageBoilQnt),
inflwSwageAltdQnt: toNumber(d.inflwSwageAltdQnt), inflwSwageAltdQnt: toNumber(d.inflwSwageAltdQnt),
@@ -296,18 +315,19 @@ const PublicSewageFacilityInp = () => {
prcsAltdCpct: toNumber(d.prcsAltdCpct), prcsAltdCpct: toNumber(d.prcsAltdCpct),
prxyCtrtCn: normalizePrxyCtrtCn(d.prxyCtrtCn), prxyCtrtCn: normalizePrxyCtrtCn(d.prxyCtrtCn),
sftyAcdntEvntcsCnt: toNumber(d.sftyAcdntEvntcsCnt), sftyAcdntEvntcsCnt: toNumber(d.sftyAcdntEvntcsCnt),
rmrk: toNullableString(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
})); }));
}; };
const getNoApplicableForRequest = (): boolean => { const getNoApplicableForRequest = (): boolean => {
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
const searchContextForToolbar = lastSearchedValues ?? searchValues; const currentData =
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
const enterable = computeTabEnterable(activeTabStatus, inputPeriodEnterable); return buildNoApplicableForRequest(
const showEnterableToolbarButtons = enterable && gugunOk; searchContextForToolbar,
const noApplicableVisible = showEnterableToolbarButtons && showIsNotApplicable; Boolean(isNotApplicable),
return noApplicableVisible ? Boolean(isNotApplicable) : false; buildFacilitySaveList(currentData).length === 0,
);
}; };
const isCheckedRow = (row: Record<string, string | number>): boolean => { 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'; 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 executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList); const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
if (verificationBlocked) { if (verificationBlocked) {
@@ -330,7 +372,7 @@ const PublicSewageFacilityInp = () => {
return; return;
} }
const noApplicable = getNoApplicableForRequest(); const noApplicable = getNoApplicableForRequest();
const treatAsNoApplicable = noApplicable || isNotApplicable; const treatAsNoApplicable = noApplicable;
const currentData = const currentData =
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? []; st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
@@ -341,7 +383,7 @@ const PublicSewageFacilityInp = () => {
} }
const submitList = treatAsNoApplicable ? [] : buildFacilitySaveList(currentData); const submitList = treatAsNoApplicable ? [] : buildFacilitySaveList(currentData);
if (!treatAsNoApplicable && submitList.length === 0) { if (!treatAsNoApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(activeTab) ?? lastSearchedValues)) {
alert('해당사항 없음을 체크해주세요.'); alert('해당사항 없음을 체크해주세요.');
return; return;
} }
@@ -354,18 +396,17 @@ const PublicSewageFacilityInp = () => {
const requestData = { const requestData = {
statsYr: lastSearchedValues.year ?? '', statsYr: lastSearchedValues.year ?? '',
ctpvNm: lastSearchedValues.sido ?? '', ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '', ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', userUuid: userInfo.userUnqId,
userId: userInfo.userUnqId,
noApplicable, noApplicable,
laePicNm: submitInfo.laePicNm, lclgvPicNm: submitInfo.lclgvPicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
laeIdfrNm: submitInfo.laeIdfrNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
submitList, submitList,
}; };
@@ -385,14 +426,15 @@ const PublicSewageFacilityInp = () => {
if (finalStatus === '5') { if (finalStatus === '5') {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel('IMR 검증 진행 중...'); setGlobalProgressLabel('시계열 검증 진행 중...');
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? '', sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? '',
stats_cd: statsCdForTab, stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? '', 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', gen_verify_stts_cd: nvpassed ? '1' : '2',
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -416,9 +458,8 @@ const PublicSewageFacilityInp = () => {
); );
await openVerificationResultDialogIfNeeded( await openVerificationResultDialogIfNeeded(
{ {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? '', ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : (lastSearchedValues.gugun ?? ''),
statsYr: lastSearchedValues.year ?? '', statsYr: lastSearchedValues.year ?? '',
statsCd: statsCdForTab, statsCd: statsCdForTab,
}, },
@@ -433,9 +474,8 @@ const PublicSewageFacilityInp = () => {
); );
await openVerificationResultDialogIfNeeded( await openVerificationResultDialogIfNeeded(
{ {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? '', ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : (lastSearchedValues.gugun ?? ''),
statsYr: lastSearchedValues.year ?? '', statsYr: lastSearchedValues.year ?? '',
statsCd: statsCdForTab, statsCd: statsCdForTab,
}, },
@@ -457,9 +497,8 @@ const PublicSewageFacilityInp = () => {
); );
await openVerificationResultDialogIfNeeded( await openVerificationResultDialogIfNeeded(
{ {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? '', ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : (lastSearchedValues.gugun ?? ''),
statsYr: lastSearchedValues.year ?? '', statsYr: lastSearchedValues.year ?? '',
statsCd: statsCdForTab, statsCd: statsCdForTab,
}, },
@@ -489,7 +528,7 @@ const PublicSewageFacilityInp = () => {
if (activeTab === 'ST00060002') { if (activeTab === 'ST00060002') {
const statusCodeResponse = await statisticsApi.CheckStatusCode({ const statusCodeResponse = await statisticsApi.CheckStatusCode({
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', sewerBzmnCd: toInputSearchRegion(sidoListRef.current, lastSearchedValues).sewerBzmnCd,
statsCd: 'ST00060001', statsCd: 'ST00060001',
statsYr: lastSearchedValues.year ?? '' statsYr: lastSearchedValues.year ?? ''
}); });
@@ -523,19 +562,36 @@ const PublicSewageFacilityInp = () => {
return; return;
} }
const saveList: PublicSewageMainFacilityMappingSaveItem[] = rightRows const checkedRows = rightRows.filter((r) => isCheckedRow(r));
.filter((r) => isCheckedRow(r)) const exclusiveSelf = hasCheckedSelfMapping(rightRows, selectedMainFacilitySn);
.map((r) => ({ const saveList: PublicSewageMainFacilityMappingSaveItem[] = (
fcltSn: String(r.fcltSn ?? ''), exclusiveSelf
admdstCd: String(r.admdstCd ?? ''), ? checkedRows.filter((r) => isSelfMappingTarget(r, selectedMainFacilitySn))
admdstNm: String(r.admdstNm ?? ''), : checkedRows
swtrmtFcltCd: String(r.swtrmtFcltCd ?? ''), ).map((r) => ({
swtrmtFcltNm: String(r.swtrmtFcltNm ?? ''), fcltSn: nullableTrimmedString(r.fcltSn),
isMainYn: String(r.isMainYn ?? ''), 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 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 ?? '')) .map((r) => String(r.fcltSn ?? ''))
.filter((sn) => sn !== ''); .filter((sn) => sn !== '');
@@ -549,7 +605,7 @@ const PublicSewageFacilityInp = () => {
mainFacilityMappingSaveDraftRef.current = { mainFacilityMappingSaveDraftRef.current = {
statsYr: lastSearchedValues.year ?? '', statsYr: lastSearchedValues.year ?? '',
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', sewerBzmnCd: toInputSearchRegion(sidoListRef.current, lastSearchedValues).sewerBzmnCd,
admdstCd: String(selectedMain.admdstCd ?? ''), admdstCd: String(selectedMain.admdstCd ?? ''),
admdstNm: String(selectedMain.admdstNm ?? ''), admdstNm: String(selectedMain.admdstNm ?? ''),
mainFcltSn: String(selectedMain.fcltSn ?? ''), mainFcltSn: String(selectedMain.fcltSn ?? ''),
@@ -580,7 +636,7 @@ const PublicSewageFacilityInp = () => {
if (activeTab === 'ST00060001') { if (activeTab === 'ST00060001') {
const noApplicable = getNoApplicableForRequest(); const noApplicable = getNoApplicableForRequest();
const treatAsNoApplicable = noApplicable || isNotApplicable; const treatAsNoApplicable = noApplicable;
const currentData = const currentData =
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? []; st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
const prxyCtrtCnError = validatePrxyCtrtCnInRows(currentData); const prxyCtrtCnError = validatePrxyCtrtCnInRows(currentData);
@@ -589,7 +645,7 @@ const PublicSewageFacilityInp = () => {
return; return;
} }
const saveList = treatAsNoApplicable ? [] : buildFacilitySaveList(currentData); const saveList = treatAsNoApplicable ? [] : buildFacilitySaveList(currentData);
if (!treatAsNoApplicable && saveList.length === 0) { if (!treatAsNoApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(activeTab) ?? lastSearchedValues)) {
alert('해당사항 없음을 체크해주세요.'); alert('해당사항 없음을 체크해주세요.');
return; return;
} }
@@ -600,9 +656,8 @@ const PublicSewageFacilityInp = () => {
const param: PublicSewageFacilitySaveRequest = { const param: PublicSewageFacilitySaveRequest = {
statsYr: lastSearchedValues.year ?? '', statsYr: lastSearchedValues.year ?? '',
ctpvNm: lastSearchedValues.sido ?? '', ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '', ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', userUuid: userInfo.userUnqId,
userId: userInfo.userUnqId,
...(isAdminInfo ? { superAdmin: 'superAdmin' } : {}), ...(isAdminInfo ? { superAdmin: 'superAdmin' } : {}),
noApplicable, noApplicable,
saveList, saveList,
@@ -644,35 +699,28 @@ const PublicSewageFacilityInp = () => {
const firstTabKey = PublicSewageFactInpColumnList[0]?.tabKey ?? 'ST00060001'; const firstTabKey = PublicSewageFactInpColumnList[0]?.tabKey ?? 'ST00060001';
const searchParamRef = useRef<PublicSewageFacilityRequestParams | null>(null); const searchParamRef = useRef<PublicSewageFacilityRequestParams | null>(null);
const activeTabId = useTabStore((s) => s.activeTabId);
useEffect(() => { 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; let cancelled = false;
void (async () => { void (async () => {
const entries = await Promise.all( const next = await fetchColumnGuidesRecord(tabKey);
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;
}
}),
);
if (cancelled) return; if (cancelled) return;
setColumnGuidesByTab(Object.fromEntries(entries)); setColumnGuidesByTab((prev) =>
Object.prototype.hasOwnProperty.call(prev, tabKey)
? prev
: { ...prev, [tabKey]: next },
);
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, [activeTabId, activeTab, columnGuidesByTab]);
const commitToolbarSnapshotForLoadedTab = ( const commitToolbarSnapshotForLoadedTab = (
tabKey: string, tabKey: string,
@@ -695,12 +743,18 @@ const PublicSewageFacilityInp = () => {
); );
return next; return next;
}); });
if (searchContext) {
setSearchContextByTab((prev) => {
const next = new Map(prev);
next.set(tabKey, searchContext);
return next;
});
}
}; };
const buildPublicSewageFacilityParams = (values: SearchFormValues): PublicSewageFacilityRequestParams => ({ const buildPublicSewageFacilityParams = (values: SearchFormValues): PublicSewageFacilityRequestParams => ({
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === 'ALL' ? '' : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
sewerBzmnCd: values.bsnmCd,
fcltNm: values.fctName, fcltNm: values.fctName,
statsYr: values.year, statsYr: values.year,
}); });
@@ -730,7 +784,11 @@ const PublicSewageFacilityInp = () => {
const statusInfo = response.data.statusInfo ?? null; const statusInfo = response.data.statusInfo ?? null;
const statsCd = response.data.commonInfo.statsCd || tabKey; const statsCd = response.data.commonInfo.statsCd || tabKey;
const commonInfo = response.data.commonInfo; const commonInfo = response.data.commonInfo;
const list = response.data.publicSewageFacilityList ?? []; const list = filterBySelectedAdmdstCd(
response.data.publicSewageFacilityList ?? [],
sidoListRef.current,
searchContext,
);
const convertedData = list.map((item) => ({ const convertedData = list.map((item) => ({
ctpvNm: commonInfo.ctpvNm, ctpvNm: commonInfo.ctpvNm,
...item, ...item,
@@ -759,6 +817,9 @@ const PublicSewageFacilityInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
setRowDataMap((prev) => { setRowDataMap((prev) => {
const next = new Map(prev); const next = new Map(prev);
next.set('ST00060001', gridRows); next.set('ST00060001', gridRows);
@@ -806,6 +867,9 @@ const PublicSewageFacilityInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, response.data.commonInfo), patchVerificationFailedByTab(prev, tabKey, response.data.commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, response.data.commonInfo),
);
setRowDataMap((prev) => { setRowDataMap((prev) => {
const next = new Map(prev); const next = new Map(prev);
next.set('MainFacility', leftGridRows); next.set('MainFacility', leftGridRows);
@@ -837,9 +901,8 @@ const PublicSewageFacilityInp = () => {
const request = { const request = {
statsYr: lastSearchedValues.year ?? '', statsYr: lastSearchedValues.year ?? '',
ctpvNm: lastSearchedValues.sido ?? '', ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '', ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', userUuid: userInfo.userUnqId,
userId: userInfo.userUnqId,
}; };
const response = const response =
@@ -886,7 +949,7 @@ const PublicSewageFacilityInp = () => {
admdstNm: draft.admdstNm, admdstNm: draft.admdstNm,
mainFcltSn: draft.mainFcltSn, mainFcltSn: draft.mainFcltSn,
mainFcltNm: draft.mainFcltNm, mainFcltNm: draft.mainFcltNm,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: draft.saveList, saveList: draft.saveList,
deleteList: draft.deleteList, deleteList: draft.deleteList,
}; };
@@ -968,7 +1031,9 @@ const PublicSewageFacilityInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
} }
@@ -1031,7 +1096,9 @@ const PublicSewageFacilityInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setInputPeriodEnterable(true); setInputPeriodEnterable(true);
periodEnterableRef.current = true; periodEnterableRef.current = true;
@@ -1058,11 +1125,10 @@ const PublicSewageFacilityInp = () => {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const statsCdForCancel = tabStatsCdMap.get(activeTab) ?? activeTab; const statsCdForCancel = tabStatsCdMap.get(activeTab) ?? activeTab;
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues?.bsnmCd ?? '', ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues?.sido ?? '', ctpvNm: lastSearchedValues?.sido ?? '',
sggNm: lastSearchedValues?.gugun === 'ALL' ? '' : lastSearchedValues?.gugun ?? '',
statsYr: lastSearchedValues?.year ?? '', statsYr: lastSearchedValues?.year ?? '',
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCdForCancel, statsCd: statsCdForCancel,
}; };
@@ -1089,13 +1155,16 @@ const PublicSewageFacilityInp = () => {
const getGridColumnInfo = ( const getGridColumnInfo = (
tab: ColumnInfoList, tab: ColumnInfoList,
guideTabKey?: string, guideTabKey?: string,
canEdit = true,
): ColumnInfo => ({ ): ColumnInfo => ({
columns: (tab.columns ?? []).map((col) => { columns: (tab.columns ?? []).map((col) => {
const guide = columnGuidesByTab[guideTabKey ?? tab.tabKey]?.[col.fieldName]?.trim(); const guide = columnGuidesByTab[guideTabKey ?? tab.tabKey]?.[col.fieldName]?.trim();
return { const withGuide = {
...col, ...col,
tooltip: guide && guide.length > 0 ? guide : undefined, tooltip: guide && guide.length > 0 ? guide : undefined,
}; };
if (canEdit) return withGuide;
return { ...withGuide, editable: false as const };
}), }),
columnLayout: tab.columnLayout ?? [], columnLayout: tab.columnLayout ?? [],
}); });
@@ -1115,7 +1184,9 @@ const PublicSewageFacilityInp = () => {
const param: PublicSewageMappingTargetsRequestParams = { const param: PublicSewageMappingTargetsRequestParams = {
sewerBzmnCd: searchParamRef.current.sewerBzmnCd ?? '', sewerBzmnCd: searchParamRef.current.sewerBzmnCd ?? '',
ctpvNm: searchParamRef.current.ctpvNm ?? '', ctpvNm: searchParamRef.current.ctpvNm ?? '',
sggNm: searchParamRef.current.sggNm ?? '', ...(searchParamRef.current.sggNm != null
? { sggNm: searchParamRef.current.sggNm }
: {}),
statsYr: searchParamRef.current.statsYr ?? '', statsYr: searchParamRef.current.statsYr ?? '',
fcltSn, fcltSn,
}; };
@@ -1131,13 +1202,18 @@ const PublicSewageFacilityInp = () => {
if (rawList.length === 0) { if (rawList.length === 0) {
alert('해당 시설은 중심처리시설의 위탁시설로 연결되어있습니다.'); alert('해당 시설은 중심처리시설의 위탁시설로 연결되어있습니다.');
} }
const mappedTargetRows = rawList.map((item) => ({ const mappedTargetRows = applyExclusiveSelfMappingChecks(
...item, toGridRows(
__checked: item.mappedYn === 'Y' ? 'Y' : '', rawList.map((item) => ({
})); ...item,
__checked: item.mappedYn === 'Y' ? 'Y' : '',
})),
),
fcltSn,
);
setRowDataMap((prev) => { setRowDataMap((prev) => {
const next = new Map(prev); const next = new Map(prev);
next.set('MainFacilityTarget', toGridRows(mappedTargetRows)); next.set('MainFacilityTarget', mappedTargetRows);
return next; return next;
}); });
} catch { } catch {
@@ -1152,8 +1228,9 @@ const PublicSewageFacilityInp = () => {
return String(row.fcltSn ?? '') === selectedMainFacilitySn ? 'rg-cell-selected-row' : undefined; 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 gugunOkOrNotDoList = buildGugunOkForToolbar(searchContextForToolbar);
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const activeTabToolbar = computeTabToolbarSnapshot( const activeTabToolbar = computeTabToolbarSnapshot(
activeTabStatus, activeTabStatus,
@@ -1173,7 +1250,7 @@ const PublicSewageFacilityInp = () => {
isMainFacilityTab && isMainFacilityTab &&
tabRowDataExists('ST00060002') && tabRowDataExists('ST00060002') &&
gugunOkOrNotDoList && gugunOkOrNotDoList &&
(isAdminInfo || inputPeriodEnterable); inputPeriodEnterable;
const showSaveInToolbar = const showSaveInToolbar =
toolbarGroupAllowed && (baseShowSaveInToolbar || showMainFacilitySaveInToolbar); toolbarGroupAllowed && (baseShowSaveInToolbar || showMainFacilitySaveInToolbar);
const showEnterableToolbarButtons = const showEnterableToolbarButtons =
@@ -1198,9 +1275,8 @@ const PublicSewageFacilityInp = () => {
const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab; const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab;
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === 'ALL' ? '' : (ctx.gugun ?? ''),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -1221,6 +1297,7 @@ const PublicSewageFacilityInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
fctName={true} fctName={true}
@@ -1241,12 +1318,12 @@ const PublicSewageFacilityInp = () => {
)} )}
{showEnterableToolbarButtons && !isMainFacilityTab && ( {showEnterableToolbarButtons && !isMainFacilityTab && (
<> <>
{showIsNotApplicable && <NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />} {showIsNotApplicable && submitOk && <NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />}
<UpdateOperateDataButton updateOperateDataButtonFunc={updateOperateDataButtonFunc} /> <UpdateOperateDataButton updateOperateDataButtonFunc={updateOperateDataButtonFunc} />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{!isMainFacilityTab && activeTabStatus?.laeSbmsnSttsCd === '2' && ( {!isMainFacilityTab && activeTabStatus?.lclgvSbmsnSttsCd === '2' && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
@@ -1270,14 +1347,28 @@ const PublicSewageFacilityInp = () => {
</TabsList> </TabsList>
{PublicSewageFactInpColumnList.map((tab) => { {PublicSewageFactInpColumnList.map((tab) => {
const tabStatus = tabStatusInfoMap.get(tab.tabKey) ?? null;
const tabEnterableForGrid =
(computeTabEnterable(tabStatus, inputPeriodEnterable) ||
isAdminInfo) &&
inputPeriodEnterable;
if (tab.isSplitGrid && tab.left && tab.right) { if (tab.isSplitGrid && tab.left && tab.right) {
const leftKey = tab.left.tabKey ?? `${tab.tabKey}_left`; const leftKey = tab.left.tabKey ?? `${tab.tabKey}_left`;
const rightKey = tab.right.tabKey ?? `${tab.tabKey}_right`; const rightKey = tab.right.tabKey ?? `${tab.tabKey}_right`;
return ( return (
<TabsContent key={tab.tabKey} value={tab.tabKey}> <TabsContent key={tab.tabKey} value={tab.tabKey}>
<SplitDataGrid <SplitDataGrid
leftColumnInfo={getGridColumnInfo(tab.left, tab.tabKey)} leftColumnInfo={getGridColumnInfo(
rightColumnInfo={getGridColumnInfo(tab.right, tab.tabKey)} tab.left,
tab.tabKey,
tabEnterableForGrid,
)}
rightColumnInfo={getGridColumnInfo(
tab.right,
tab.tabKey,
tabEnterableForGrid,
)}
leftFixedColumns={tab.left.fixedColumns} leftFixedColumns={tab.left.fixedColumns}
rightFixedColumns={tab.right.fixedColumns} rightFixedColumns={tab.right.fixedColumns}
showHeaderCheckbox={true} showHeaderCheckbox={true}
@@ -1303,11 +1394,25 @@ const PublicSewageFacilityInp = () => {
const next = new Map(prev); const next = new Map(prev);
const rows = [...(next.get('MainFacilityTarget') ?? [])]; const rows = [...(next.get('MainFacilityTarget') ?? [])];
if (!rows[itemIndex]) return prev; if (!rows[itemIndex]) return prev;
const clicked = rows[itemIndex];
if (
checked &&
hasCheckedSelfMapping(rows, selectedMainFacilitySn) &&
!isSelfMappingTarget(clicked, selectedMainFacilitySn)
) {
return prev;
}
rows[itemIndex] = { rows[itemIndex] = {
...rows[itemIndex], ...clicked,
__checked: checked ? 'Y' : '', __checked: checked ? 'Y' : '',
}; };
next.set('MainFacilityTarget', rows); next.set(
'MainFacilityTarget',
applyExclusiveSelfMappingChecks(
rows,
selectedMainFacilitySn,
),
);
return next; return next;
}); });
} }
@@ -1318,16 +1423,59 @@ const PublicSewageFacilityInp = () => {
? (checked) => { ? (checked) => {
setRowDataMap((prev) => { setRowDataMap((prev) => {
const next = new Map(prev); const next = new Map(prev);
const rows = [...(next.get('MainFacilityTarget') ?? [])].map((r) => ({ const rows = [...(next.get('MainFacilityTarget') ?? [])];
...r, if (
__checked: checked ? 'Y' : '', checked &&
})); hasCheckedSelfMapping(rows, selectedMainFacilitySn)
next.set('MainFacilityTarget', rows); ) {
next.set(
'MainFacilityTarget',
applyExclusiveSelfMappingChecks(
rows,
selectedMainFacilitySn,
),
);
return next;
}
next.set(
'MainFacilityTarget',
rows.map((r) => ({
...r,
__checked:
checked &&
!isSelfMappingTarget(
r,
selectedMainFacilitySn,
)
? 'Y'
: '',
})),
);
return next; return next;
}); });
} }
: undefined : undefined
} }
rightCheckableCallback={
tab.tabKey === 'ST00060002'
? (row) => {
const rows =
rowDataMap.get('MainFacilityTarget') ?? [];
if (
!hasCheckedSelfMapping(
rows,
selectedMainFacilitySn,
)
) {
return true;
}
return isSelfMappingTarget(
row,
selectedMainFacilitySn,
);
}
: undefined
}
setLeftRowData={(updater) => { setLeftRowData={(updater) => {
setRowDataMap((prev) => { setRowDataMap((prev) => {
const next = new Map(prev); const next = new Map(prev);
@@ -1357,11 +1505,10 @@ const PublicSewageFacilityInp = () => {
); );
} }
const gridColumnInfo = getGridColumnInfo(tab); const gridColumnInfo = getGridColumnInfo(
const tabStatus = tabStatusInfoMap.get(tab.tabKey) ?? null; tab,
const tabEnterableForGrid = computeTabEnterable( undefined,
tabStatus, tabEnterableForGrid,
inputPeriodEnterable,
); );
const excelToolbarEnabledForTab = const excelToolbarEnabledForTab =
tabRowDataExists(tab.tabKey) && tabRowDataExists(tab.tabKey) &&
@@ -1371,7 +1518,7 @@ const PublicSewageFacilityInp = () => {
return ( return (
<TabsContent key={tab.tabKey} value={tab.tabKey}> <TabsContent key={tab.tabKey} value={tab.tabKey}>
<DataGrid <DataGrid
key={tab.tabKey} key={`${tab.tabKey}-${tabEnterableForGrid ? 'enterable' : 'readonly'}`}
columnInfo={gridColumnInfo} columnInfo={gridColumnInfo}
rowStartNum={tab.rowStartNum} rowStartNum={tab.rowStartNum}
excelExportFileSuffix={tab.tabLabel || tab.tabKey} excelExportFileSuffix={tab.tabLabel || tab.tabKey}
@@ -1400,14 +1547,29 @@ const PublicSewageFacilityInp = () => {
showHeaderCheckbox={false} showHeaderCheckbox={false}
enableSort={false} enableSort={false}
cellStyleCallback={({ row, fieldName }) => { cellStyleCallback={({ row, fieldName }) => {
const lockPrxyCtrtCn =
fieldName === 'prxyCtrtCn' && isMainFacilityRow(row);
const verification = (row as GridRowWithVerification).verificationResult; const verification = (row as GridRowWithVerification).verificationResult;
if (!verification) return; let verifyStyle: string | undefined;
const errors = verification[fieldName]; if (verification) {
if (Array.isArray(errors) && errors.length > 0) { const errors = verification[fieldName];
const hasImr = errors.some((e) => e.vrfcCd === "IMR" || e.vrfcCd === "HB"); if (Array.isArray(errors) && errors.length > 0) {
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error"; 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 }) => { cellTooltipCallback={({ row, fieldName }) => {
const verification = (row as GridRowWithVerification).verificationResult; const verification = (row as GridRowWithVerification).verificationResult;
if (!verification) return; if (!verification) return;
@@ -1416,9 +1578,9 @@ const PublicSewageFacilityInp = () => {
return errors return errors
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" ? "IMR검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "HB검증 - "); e.vrfcCd === "IMR" ? "시계열 검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - ");
const comp = e.compVlCn ?? ""; const comp = e.cmprCn ?? "";
const now = e.nowVlCn ?? ""; const now = e.nowCn ?? "";
return `${prefix}${e.rsnExpln} return `${prefix}${e.rsnExpln}
(검증 기준값: ${comp} / 현재 입력값: ${now})`; (검증 기준값: ${comp} / 현재 입력값: ${now})`;
}) })
@@ -1,20 +1,56 @@
import { import {
type RefObject,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useState, useState,
type RefObject,
} from "react"; } from "react";
import { flushSync } from "react-dom"; import { flushSync } from "react-dom";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { ValueType } from "realgrid"; import { ValueType } from "realgrid";
import type { RealGridReact } from "realgrid-react"; 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 { LoadingWithAnimation } from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import SubmitDailog from "./SubmitDailog";
import AddArticleButton from "@/components/button/AddArticleButton"; import AddArticleButton from "@/components/button/AddArticleButton";
import CancelSubmitButton from "@/components/button/CancelSubmitButton"; import CancelSubmitButton from "@/components/button/CancelSubmitButton";
import DeleteArticleButton from "@/components/button/DeleteArticleButton"; import DeleteArticleButton from "@/components/button/DeleteArticleButton";
@@ -24,49 +60,24 @@ import SaveButton from "@/components/button/SaveButton";
import SubmitButton from "@/components/button/SumitButton"; import SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton"; import VerificationResultButton from "@/components/button/VerificationResultButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results"; import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import { PumpingStationInpColumn } from "@/components/util/column/input/PumpingStationInpColumn";
import SubmitDailog from "./SubmitDailog";
import type { import type {
CellStyleCallbackParams, CellStyleCallbackParams,
CellTooltipCallbackParams, CellTooltipCallbackParams,
ColumnConfig, ColumnConfig,
} from "@/components/type/DataGrid.type"; } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.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 { toGridRows } from "@/components/util/grid/gridFunction";
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat"; import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
import { statisticsInputPumpingStationApi } from "@/entities/statistics/api/input/statistics-input-pumping-station-service"; import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
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 { useAdminStore } from "@/store/admin-store"; import { useAdminStore } from "@/store/admin-store";
import { useTabStore } from "@/store/tab-store";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
type GridRowWithVerification = Record<string, string | number> & { type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: PumpingStationVerificationResult; verificationResult?: PumpingStationVerificationResult;
@@ -82,8 +93,8 @@ const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -91,10 +102,22 @@ const computeTabEnterable = (
return periodEnterable; return periodEnterable;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || "") return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== "ALL") }
: true;
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( function computeTabToolbarSnapshot(
@@ -107,8 +130,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = const showSubmittedInInputPeriodToolbar =
isSubmitted && periodEnterable && gugunOk; isSubmitted && periodEnterable && gugunOk;
@@ -128,9 +151,8 @@ function buildPumpingStationSearchParam(
const fcltNm = values.fctName?.trim(); const fcltNm = values.fctName?.trim();
return { return {
statsYr: values.year, statsYr: values.year,
sewerBzmnCd: values.bsnmCd,
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
...(fcltNm ? { fcltNm } : {}), ...(fcltNm ? { fcltNm } : {}),
...(overrides?.mode ? { mode: overrides.mode } : {}), ...(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 { function toGridNumber(value: string | number | null | undefined): number {
const n = Number(value); const n = Number(value);
return Number.isFinite(n) ? n : 0; return Number.isFinite(n) ? n : 0;
@@ -205,9 +220,9 @@ function toGridNumber(value: string | number | null | undefined): number {
function resolvePumpingStationAdmdst( function resolvePumpingStationAdmdst(
row: Record<string, string | number>, row: Record<string, string | number>,
sggOptions: Sgg[], sggOptions: Sgg[],
): { admdstCd: string; admdstNm: string } { ): { admdstCd: string | null; admdstNm: string | null } {
const existingCd = String(row.admdstCd ?? "").trim(); const existingCd = nullableTrimmedString(row.admdstCd);
const existingNm = String(row.admdstNm ?? "").trim(); const existingNm = nullableTrimmedString(row.admdstNm);
if (existingCd && existingNm) { if (existingCd && existingNm) {
return { admdstCd: existingCd, admdstNm: existingNm }; return { admdstCd: existingCd, admdstNm: existingNm };
} }
@@ -216,7 +231,10 @@ function resolvePumpingStationAdmdst(
if (matched) { if (matched) {
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun }; return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
} }
return { admdstCd: existingCd, admdstNm: existingNm || sggNm }; return {
admdstCd: existingCd,
admdstNm: existingNm ?? nullableTrimmedString(sggNm),
};
} }
/** 그리드 행 → ST00050001 저장/삭제 API 항목 */ /** 그리드 행 → ST00050001 저장/삭제 API 항목 */
@@ -225,44 +243,43 @@ function buildPumpingStationSaveItem(
sggOptions: Sgg[] = [], sggOptions: Sgg[] = [],
): PumpingStationSaveItem { ): PumpingStationSaveItem {
const { admdstCd, admdstNm } = resolvePumpingStationAdmdst(row, sggOptions); const { admdstCd, admdstNm } = resolvePumpingStationAdmdst(row, sggOptions);
const fcltSn = String(row.fcltSn ?? "").trim();
return { return {
admdstCd, admdstCd,
admdstNm, admdstNm,
fcltSn: fcltSn, fcltSn: nullableTrimmedString(row.fcltSn),
swtrmtFcltNm: String(row.swtrmtFcltNm ?? ""), swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
fcltNm: String(row.fcltNm ?? ""), fcltNm: nullableTrimmedString(row.fcltNm),
pumpSeNm: String(row.pumpSeNm ?? ""), pumpSeNm: nullableTrimmedString(row.pumpSeNm),
fcltPstnCn: String(row.fcltPstnCn ?? ""), fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd), oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd),
planMltvArea: toGridNumber(row.planMltvArea), planDrnar: toGridNumber(row.planDrnar),
sndyArea: toGridNumber(row.sndyArea), icodwArea: toGridNumber(row.icodwArea),
rainArea: toGridNumber(row.rainArea), rainArea: toGridNumber(row.rainArea),
drtwPumpN1DmtrArea: toGridNumber(row.drtwPumpN1DmtrArea), drtwPumpN1Dmtr: toGridNumber(row.drtwPumpN1Dmtr),
drtwPumpN1Ppqty: toGridNumber(row.drtwPumpN1Ppqty), drtwPumpN1Ppqty: toGridNumber(row.drtwPumpN1Ppqty),
drtwPumpN1Cnt: toGridNumber(row.drtwPumpN1Cnt), drtwPumpN1Cnt: toGridNumber(row.drtwPumpN1Cnt),
drtwPumpN1RsrvCnt: toGridNumber(row.drtwPumpN1RsrvCnt), drtwPumpN1RsrvCnt: toGridNumber(row.drtwPumpN1RsrvCnt),
drtwPumpN2DmtrArea: toGridNumber(row.drtwPumpN2DmtrArea), drtwPumpN2Dmtr: toGridNumber(row.drtwPumpN2Dmtr),
drtwPumpN2Ppqty: toGridNumber(row.drtwPumpN2Ppqty), drtwPumpN2Ppqty: toGridNumber(row.drtwPumpN2Ppqty),
drtwPumpN2Cnt: toGridNumber(row.drtwPumpN2Cnt), drtwPumpN2Cnt: toGridNumber(row.drtwPumpN2Cnt),
drtwPumpN2RsrvCnt: toGridNumber(row.drtwPumpN2RsrvCnt), drtwPumpN2RsrvCnt: toGridNumber(row.drtwPumpN2RsrvCnt),
drtwPumpN3DmtrArea: toGridNumber(row.drtwPumpN3DmtrArea), drtwPumpN3Dmtr: toGridNumber(row.drtwPumpN3Dmtr),
drtwPumpN3Ppqty: toGridNumber(row.drtwPumpN3Ppqty), drtwPumpN3Ppqty: toGridNumber(row.drtwPumpN3Ppqty),
drtwPumpN3Cnt: toGridNumber(row.drtwPumpN3Cnt), drtwPumpN3Cnt: toGridNumber(row.drtwPumpN3Cnt),
drtwPumpN3RsrvCnt: toGridNumber(row.drtwPumpN3RsrvCnt), drtwPumpN3RsrvCnt: toGridNumber(row.drtwPumpN3RsrvCnt),
exlnPumpN1DmtrArea: toGridNumber(row.exlnPumpN1DmtrArea), exlnPumpN1Dmtr: toGridNumber(row.exlnPumpN1Dmtr),
exlnPumpN1Ppqty: toGridNumber(row.exlnPumpN1Ppqty), exlnPumpN1Ppqty: toGridNumber(row.exlnPumpN1Ppqty),
exlnPumpN1Cnt: toGridNumber(row.exlnPumpN1Cnt), exlnPumpN1Cnt: toGridNumber(row.exlnPumpN1Cnt),
exlnPumpN1RsrvCnt: toGridNumber(row.exlnPumpN1RsrvCnt), exlnPumpN1RsrvCnt: toGridNumber(row.exlnPumpN1RsrvCnt),
exlnPumpN2DmtrArea: toGridNumber(row.exlnPumpN2DmtrArea), exlnPumpN2Dmtr: toGridNumber(row.exlnPumpN2Dmtr),
exlnPumpN2Ppqty: toGridNumber(row.exlnPumpN2Ppqty), exlnPumpN2Ppqty: toGridNumber(row.exlnPumpN2Ppqty),
exlnPumpN2Cnt: toGridNumber(row.exlnPumpN2Cnt), exlnPumpN2Cnt: toGridNumber(row.exlnPumpN2Cnt),
exlnPumpN2RsrvCnt: toGridNumber(row.exlnPumpN2RsrvCnt), exlnPumpN2RsrvCnt: toGridNumber(row.exlnPumpN2RsrvCnt),
exlnPumpN3DmtrArea: toGridNumber(row.exlnPumpN3DmtrArea), exlnPumpN3Dmtr: toGridNumber(row.exlnPumpN3Dmtr),
exlnPumpN3Ppqty: toGridNumber(row.exlnPumpN3Ppqty), exlnPumpN3Ppqty: toGridNumber(row.exlnPumpN3Ppqty),
exlnPumpN3Cnt: toGridNumber(row.exlnPumpN3Cnt), exlnPumpN3Cnt: toGridNumber(row.exlnPumpN3Cnt),
exlnPumpN3RsrvCnt: toGridNumber(row.exlnPumpN3RsrvCnt), exlnPumpN3RsrvCnt: toGridNumber(row.exlnPumpN3RsrvCnt),
anlElpwrUseQnt: toGridNumber(row.anlElpwrUseQnt), anlElpwrUsqty: toGridNumber(row.anlElpwrUsqty),
rmrk: nullableTrimmedString(row.rmrk), rmrk: nullableTrimmedString(row.rmrk),
}; };
} }
@@ -285,11 +302,11 @@ async function waitForImrJobCompleted(
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
function createEmptyPumpingStationRow( function createEmptyPumpingStationRow(
@@ -311,6 +328,8 @@ const PUMPING_STATION_FACILITY_NAME_GUIDE_MESSAGE =
// 하수도 통계 관리 > 05. 펌프장 (SewageReservoirPumpInp 단일 탭 패턴) // 하수도 통계 관리 > 05. 펌프장 (SewageReservoirPumpInp 단일 탭 패턴)
const PumpingStationInp = () => { const PumpingStationInp = () => {
const { sidoList } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
@@ -333,6 +352,8 @@ const PumpingStationInp = () => {
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null); const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
const [statsCd, setStatsCd] = useState(""); const [statsCd, setStatsCd] = useState("");
const [isVerificationFailed, setIsVerificationFailed] = useState(false); const [isVerificationFailed, setIsVerificationFailed] = useState(false);
const [footerVerificationResult, setFooterVerificationResult] =
useState<VerificationResultLikeMap | null>(null);
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [sggOptions, setSggOptions] = useState<Sgg[]>([]); const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
@@ -353,7 +374,10 @@ const PumpingStationInp = () => {
const sggOptionsSidoRef = useRef<string | null>(null); const sggOptionsSidoRef = useRef<string | null>(null);
const [searchLoaded, setSearchLoaded] = useState(false); const [searchLoaded, setSearchLoaded] = useState(false);
const activeTabId = useTabStore((s) => s.activeTabId);
useEffect(() => { useEffect(() => {
if (activeTabId !== STATISTICS_INPUT_URL_PATH.PUMPING_STATION) return;
let cancelled = false; let cancelled = false;
void (async () => { void (async () => {
try { try {
@@ -377,7 +401,7 @@ const PumpingStationInp = () => {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, [activeTabId]);
const loadSggOptions = useCallback(async (sido: string): Promise<Sgg[]> => { const loadSggOptions = useCallback(async (sido: string): Promise<Sgg[]> => {
if (!sido || sido === "ALL") { if (!sido || sido === "ALL") {
@@ -397,7 +421,7 @@ const PumpingStationInp = () => {
const columnInfo = useMemo(() => { const columnInfo = useMemo(() => {
const enterable = const enterable =
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo; (computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
const rawColumns = PumpingStationInpColumn.columns ?? []; const rawColumns = PumpingStationInpColumn.columns ?? [];
const base = enterable const base = enterable
? rawColumns ? rawColumns
@@ -425,7 +449,7 @@ const PumpingStationInp = () => {
}, [statusInfo, inputPeriodEnterable, isAdminInfo, sggOptions, columnGuidesByEngCol]); }, [statusInfo, inputPeriodEnterable, isAdminInfo, sggOptions, columnGuidesByEngCol]);
const canEditGrid = const canEditGrid =
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo; (computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
const getCurrentRows = (): Record<string, string | number>[] => const getCurrentRows = (): Record<string, string | number>[] =>
gridGetRowsRef.current?.() ?? rowData; gridGetRowsRef.current?.() ?? rowData;
@@ -457,7 +481,11 @@ const PumpingStationInp = () => {
const { commonInfo, statusInfo: nextStatusInfo, pumpingStationList } = const { commonInfo, statusInfo: nextStatusInfo, pumpingStationList } =
response.data; response.data;
const list = pumpingStationList ?? []; const list = filterBySelectedAdmdstCd(
pumpingStationList ?? [],
sidoListRef.current,
values,
);
const gridRows = mapPumpingStationListToGridRows(list, commonInfo.ctpvNm); const gridRows = mapPumpingStationListToGridRows(list, commonInfo.ctpvNm);
setPumpingStationDeleteList([]); setPumpingStationDeleteList([]);
@@ -465,6 +493,7 @@ const PumpingStationInp = () => {
setStatusInfo(nextStatusInfo); setStatusInfo(nextStatusInfo);
setStatsCd(commonInfo.statsCd); setStatsCd(commonInfo.statsCd);
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo)); setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
setFooterVerificationResult(commonInfo.verificationResult ?? null);
if (overrides?.mode !== "PRE") { if (overrides?.mode !== "PRE") {
setIsNotApplicable(gridRows.length === 0); setIsNotApplicable(gridRows.length === 0);
} else if (gridRows.length > 0) { } else if (gridRows.length > 0) {
@@ -524,6 +553,7 @@ const PumpingStationInp = () => {
setStatusInfo(null); setStatusInfo(null);
setStatsCd(""); setStatsCd("");
setIsVerificationFailed(false); setIsVerificationFailed(false);
setFooterVerificationResult(null);
setIsNotApplicable(false); setIsNotApplicable(false);
setInputPeriodEnterable(true); setInputPeriodEnterable(true);
setSggOptions([]); setSggOptions([]);
@@ -573,14 +603,10 @@ const PumpingStationInp = () => {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCd || PUMPING_STATION_STATS_CD, statsCd: statsCd || PUMPING_STATION_STATS_CD,
}; };
@@ -613,9 +639,9 @@ const PumpingStationInp = () => {
return; return;
} }
const noApplicable = isNotApplicable; const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
const currentData = getCurrentRows(); const currentData = getCurrentRows();
if (!noApplicable && currentData.length === 0) { if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -642,7 +668,7 @@ const PumpingStationInp = () => {
if (!(await confirm("저장 하시겠습니까?"))) return; if (!(await confirm("저장 하시겠습니까?"))) return;
commitGrid(); commitGrid();
const noApplicable = isNotApplicable; const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
const currentData = getCurrentRows(); const currentData = getCurrentRows();
const saveList = noApplicable const saveList = noApplicable
? [] ? []
@@ -653,7 +679,7 @@ const PumpingStationInp = () => {
.filter((row) => String(row.fcltSn ?? "").trim() !== "") .filter((row) => String(row.fcltSn ?? "").trim() !== "")
.map((row) => buildPumpingStationSaveItem(row, sggOptions)); .map((row) => buildPumpingStationSaveItem(row, sggOptions));
if (!noApplicable && saveList.length === 0) { if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -664,12 +690,8 @@ const PumpingStationInp = () => {
const response = await statisticsInputPumpingStationApi.savePumpingStationList({ const response = await statisticsInputPumpingStationApi.savePumpingStationList({
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL" userUuid: userInfo.userUnqId,
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
userId: userInfo.userUnqId,
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
noApplicable, noApplicable,
saveList, saveList,
@@ -702,7 +724,7 @@ const PumpingStationInp = () => {
} }
commitGrid(); commitGrid();
const noApplicable = isNotApplicable; const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
const currentData = getCurrentRows(); const currentData = getCurrentRows();
const submitList = noApplicable const submitList = noApplicable
? [] ? []
@@ -711,19 +733,15 @@ const PumpingStationInp = () => {
.filter((row) => String(row.fcltSn ?? "").trim() !== "") .filter((row) => String(row.fcltSn ?? "").trim() !== "")
.map((row) => buildPumpingStationSaveItem(row, sggOptions)); .map((row) => buildPumpingStationSaveItem(row, sggOptions));
if (!noApplicable && submitList.length === 0) { if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
const statsCdForSubmit = statsCd || PUMPING_STATION_STATS_CD; const statsCdForSubmit = statsCd || PUMPING_STATION_STATS_CD;
const verificationQuery = { const verificationQuery = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
statsCd: statsCdForSubmit, statsCd: statsCdForSubmit,
}; };
@@ -740,20 +758,16 @@ const PumpingStationInp = () => {
const response = await statisticsInputPumpingStationApi.submitPumpingStationList({ const response = await statisticsInputPumpingStationApi.submitPumpingStationList({
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL" userUuid: userInfo.userUnqId,
? "" lclgvPicNm: submitInfo.lclgvPicNm,
: (lastSearchedValues.gugun ?? ""), lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
userId: userInfo.userUnqId, lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laePicNm: submitInfo.laePicNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
noApplicable, noApplicable,
submitList, submitList,
@@ -770,15 +784,16 @@ const PumpingStationInp = () => {
if (finalStatus === "5") { if (finalStatus === "5") {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중..."); setGlobalProgressLabel("시계열 검증 진행 중...");
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
stats_cd: statsCdForSubmit, stats_cd: statsCdForSubmit,
stats_yr: lastSearchedValues.year ?? "", stats_yr: lastSearchedValues.year ?? "",
lae_sbmsn_stts_cd: lae_sbmsn_stts_cd:
statusInfo?.laeSbmsnSttsCd === "4" ? "4" : "2", statusInfo?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
gen_verify_stts_cd: nvpassed ? "1" : "2", gen_verify_stts_cd: nvpassed ? "1" : "2",
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -925,19 +940,19 @@ const PumpingStationInp = () => {
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" e.vrfcCd === "IMR"
? "IMR검증 - " ? "시계열 검증 - "
: e.vrfcCd === "NV" : e.vrfcCd === "NV"
? "일반검증 - " ? "일반검증 - "
: "HB검증 -"; : "모집단 검증 -";
return `${prefix}${e.rsnExpln} return `${prefix}${e.rsnExpln}
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; (검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
} }
}; };
const searchContextForToolbar = lastSearchedValues ?? searchValues; const searchContextForToolbar = lastSearchedValues;
const toolbarSnapshot = computeTabToolbarSnapshot( const toolbarSnapshot = computeTabToolbarSnapshot(
statusInfo, statusInfo,
rowData.length > 0, rowData.length > 0,
@@ -957,23 +972,23 @@ const PumpingStationInp = () => {
hasSearched && hasSearched &&
toolbarGroupAllowed && toolbarGroupAllowed &&
(showSaveInToolbar || showEnterableToolbarButtons); (showSaveInToolbar || showEnterableToolbarButtons);
const tabEnterableForGrid =
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const showNotApplicable = const showNotApplicable =
searchLoaded && searchLoaded &&
hasSearched && hasSearched &&
rowData.length === 0 && rowData.length === 0 &&
computeTabEnterable(statusInfo, inputPeriodEnterable); computeTabEnterable(statusInfo, inputPeriodEnterable) &&
submitOk;
const tabEnterableForGrid =
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
const verificationQueryContext = (() => { const verificationQueryContext = (() => {
const ctx = lastSearchedValues ?? searchValues; const ctx = lastSearchedValues ?? searchValues;
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -989,6 +1004,7 @@ const PumpingStationInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
fctName={true} fctName={true}
@@ -1008,10 +1024,10 @@ const PumpingStationInp = () => {
<LastYearDataButton <LastYearDataButton
getLastYearDataFunc={() => void getLastYearDataFunc()} getLastYearDataFunc={() => void getLastYearDataFunc()}
/> />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{statusInfo?.laeSbmsnSttsCd === "2" && ( {statusInfo?.lclgvSbmsnSttsCd === "2" && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
@@ -1056,6 +1072,7 @@ const PumpingStationInp = () => {
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd} excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
cellStyleCallback={verificationCellStyle} cellStyleCallback={verificationCellStyle}
footerVerificationResult={footerVerificationResult}
cellTooltipCallback={verificationCellTooltip} cellTooltipCallback={verificationCellTooltip}
/> />
</div> </div>
@@ -1,11 +1,55 @@
import { useCallback, useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction } from "react"; import { type Dispatch, type SetStateAction,useCallback, useEffect, useMemo, useRef, useState } 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 { flushSync } from "react-dom";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; 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 AddArticleButton from "@/components/button/AddArticleButton";
import CancelSubmitButton from "@/components/button/CancelSubmitButton"; import CancelSubmitButton from "@/components/button/CancelSubmitButton";
@@ -16,7 +60,6 @@ import SaveButton from "@/components/button/SaveButton";
import SubmitButton from "@/components/button/SumitButton"; import SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton"; import VerificationResultButton from "@/components/button/VerificationResultButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results"; import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
@@ -30,51 +73,25 @@ import type {
ColumnInfo, ColumnInfo,
} from "@/components/type/DataGrid.type"; } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.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 { ExcretaRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/ExcretaRelatedIndustryInpColumn";
import { PrivateSewageTreatRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn"; import { PrivateSewageTreatRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn";
import { PublicInstitutionExcretaIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn"; import { PublicInstitutionExcretaIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn";
import { PublicInstitutionSewerIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn"; import { PublicInstitutionSewerIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn";
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag"; import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
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 { import {
SEWAGE_EXCRETA_EXCRETA_RELATED_INDUSTRY_STATS_CD, patchFooterVerificationByTab,
SEWAGE_EXCRETA_PRIVATE_SEWAGE_TREAT_RELATED_INDUSTRY_STATS_CD, patchVerificationFailedByTab,
SEWAGE_EXCRETA_PUBLIC_INSTITUTION_EXCRETA_STATS_CD, } from "@/components/util/input/verification-failed-flag";
SEWAGE_EXCRETA_PUBLIC_INSTITUTION_SEWER_STATS_CD, import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
} 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";
import { useAdminStore } from "@/store/admin-store"; 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,
const SewageExcretaRelatedInpTabList = [ PublicInstitutionExcretaIndustryInpColumn,
PublicInstitutionSewerIndustryInpColumn, ExcretaRelatedIndustryInpColumn,
PublicInstitutionExcretaIndustryInpColumn,
ExcretaRelatedIndustryInpColumn,
PrivateSewageTreatRelatedIndustryInpColumn, PrivateSewageTreatRelatedIndustryInpColumn,
] as const; ] as const;
@@ -141,8 +158,8 @@ const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -150,10 +167,22 @@ const computeTabEnterable = (
return periodEnterable; return periodEnterable;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || "") return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== "ALL") }
: true;
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( function computeTabToolbarSnapshot(
@@ -166,8 +195,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk; const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
const showActionToolbarCard = const showActionToolbarCard =
@@ -210,10 +239,20 @@ function isCheckedRow(row: Record<string, string | number>): boolean {
type ArticleOgnzSnSet = Set<string>; 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>(); const set = new Set<string>();
for (const row of rows) { for (const row of rows) {
const ognzSn = String(row.ognzSn ?? "").trim(); const ognzSn = getPublicInstitutionOgnzSn(row);
if (ognzSn) set.add(ognzSn); if (ognzSn) set.add(ognzSn);
} }
return set; return set;
@@ -249,7 +288,7 @@ function isPersistedPublicInstitutionArticleRow(
): boolean { ): boolean {
if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) return false; if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) return false;
if (isNewArticleRow(row)) return false; if (isNewArticleRow(row)) return false;
const ognzSn = String(row.ognzSn ?? "").trim(); const ognzSn = getPublicInstitutionOgnzSn(row);
if (!ognzSn) return false; if (!ognzSn) return false;
if ((preYearRef.current.get(tabKey) ?? new Set()).has(ognzSn)) return false; if ((preYearRef.current.get(tabKey) ?? new Set()).has(ognzSn)) return false;
return (persistedRef.current.get(tabKey) ?? new Set()).has(ognzSn); return (persistedRef.current.get(tabKey) ?? new Set()).has(ognzSn);
@@ -312,7 +351,7 @@ function buildSggNmColumn(
function resolvePublicInstitutionAdmdst( function resolvePublicInstitutionAdmdst(
row: Record<string, string | number>, row: Record<string, string | number>,
sggOptions: Sgg[], sggOptions: Sgg[],
): { admdstCd: string; admdstNm: string } { ): { admdstCd: string | null; admdstNm: string | null } {
const sggNm = String(row.sggNm ?? "").trim(); const sggNm = String(row.sggNm ?? "").trim();
if (sggNm) { if (sggNm) {
const matched = sggOptions.find((s) => s.gugun === sggNm); const matched = sggOptions.find((s) => s.gugun === sggNm);
@@ -320,9 +359,12 @@ function resolvePublicInstitutionAdmdst(
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun }; return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
} }
} }
const existingCd = String(row.admdstCd ?? "").trim(); const existingCd = nullableTrimmedString(row.admdstCd);
const existingNm = String(row.admdstNm ?? "").trim(); const existingNm = nullableTrimmedString(row.admdstNm);
return { admdstCd: existingCd, admdstNm: existingNm || sggNm }; return {
admdstCd: existingCd,
admdstNm: existingNm ?? nullableTrimmedString(sggNm),
};
} }
function mergeGridRowsWithMapRows( 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 { function toGridNumber(value: string | number | null | undefined): number {
const n = Number(value); const n = Number(value);
return Number.isFinite(n) ? n : 0; return Number.isFinite(n) ? n : 0;
@@ -378,18 +413,18 @@ function toGridNumber(value: string | number | null | undefined): number {
function buildPublicInstitutionIndustrySaveItem( function buildPublicInstitutionIndustrySaveItem(
row: Record<string, string | number>, row: Record<string, string | number>,
sggOptions: Sgg[], sggOptions: Sgg[],
): PublicInstitutionSewerIndustrySaveItem { ): PublicInstitutionSewerIndustrySaveItem | PublicInstitutionExcretaIndustrySaveItem {
const { admdstCd, admdstNm } = resolvePublicInstitutionAdmdst(row, sggOptions); const { admdstCd, admdstNm } = resolvePublicInstitutionAdmdst(row, sggOptions);
return { return {
admdstCd, admdstCd,
admdstNm, admdstNm,
ognzSn: String(row.ognzSn ?? "").trim(), statsOgnzSn: nullableTrimmedString(row.statsOgnzSn),
ognzNm: String(row.ognzNm ?? "").trim(), ognzNm: nullableTrimmedString(row.ognzNm),
pbadmsNope: toGridNumber(row.pbadmsNope), pbadmsNope: toGridNumber(row.pbadmsNope),
engrkCnt: toGridNumber(row.engrkCnt), engrkCnt: toGridNumber(row.engrkCnt),
mchnCnt: toGridNumber(row.mchnCnt), mchnCnt: toGridNumber(row.mchnCnt),
prvtmCnt: toGridNumber(row.prvtmCnt), prvtmCnt: toGridNumber(row.prvtmCnt),
chmengCnt: toGridNumber(row.chmengCnt), chemCnt: toGridNumber(row.chemCnt),
envCnt: toGridNumber(row.envCnt), envCnt: toGridNumber(row.envCnt),
enjbEtcCnt: toGridNumber(row.enjbEtcCnt), enjbEtcCnt: toGridNumber(row.enjbEtcCnt),
etcCnt: toGridNumber(row.etcCnt), etcCnt: toGridNumber(row.etcCnt),
@@ -402,13 +437,13 @@ function buildExcretaRelatedIndustrySaveItem(
row: Record<string, string | number>, row: Record<string, string | number>,
): ExcretaRelatedIndustrySaveItem { ): ExcretaRelatedIndustrySaveItem {
return { return {
admdstCd: String(row.admdstCd ?? "").trim(), admdstCd: nullableTrimmedString(row.admdstCd),
admdstNm: String(row.admdstNm ?? "").trim(), admdstNm: nullableTrimmedString(row.admdstNm),
bzentyCnt: toGridNumber(row.bzentyCnt), bzentyCnt: toGridNumber(row.bzentyCnt),
empNope: toGridNumber(row.empNope), empNope: toGridNumber(row.empNope),
n3TonUdrCnt: toGridNumber(row.n3TonUdrCnt), frlvlTonUdrCnt: toGridNumber(row.frlvlTonUdrCnt),
n4TonUdrCnt: toGridNumber(row.n4TonUdrCnt), sclvlTonUdrCnt: toGridNumber(row.sclvlTonUdrCnt),
n8TonUdrCnt: toGridNumber(row.n8TonUdrCnt), thlvlTonUdrCnt: toGridNumber(row.thlvlTonUdrCnt),
etcCnt: toGridNumber(row.etcCnt), etcCnt: toGridNumber(row.etcCnt),
clctCnvncPrfmncQnt: toGridNumber(row.clctCnvncPrfmncQnt), clctCnvncPrfmncQnt: toGridNumber(row.clctCnvncPrfmncQnt),
rmrk: nullableTrimmedString(row.rmrk), rmrk: nullableTrimmedString(row.rmrk),
@@ -420,8 +455,8 @@ function buildPrivateSewageTreatRelatedIndustrySaveItem(
row: Record<string, string | number>, row: Record<string, string | number>,
): PrivateSewageTreatRelatedIndustrySaveItem { ): PrivateSewageTreatRelatedIndustrySaveItem {
return { return {
admdstCd: String(row.admdstCd ?? "").trim(), admdstCd: nullableTrimmedString(row.admdstCd),
admdstNm: String(row.admdstNm ?? "").trim(), admdstNm: nullableTrimmedString(row.admdstNm),
designMdsrCnt: toGridNumber(row.designMdsrCnt), designMdsrCnt: toGridNumber(row.designMdsrCnt),
designMdsrNope: toGridNumber(row.designMdsrNope), designMdsrNope: toGridNumber(row.designMdsrNope),
mfbizCnt: toGridNumber(row.mfbizCnt), mfbizCnt: toGridNumber(row.mfbizCnt),
@@ -521,12 +556,12 @@ function verificationCellTooltip({
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" e.vrfcCd === "IMR"
? "IMR검증 - " ? "시계열 검증 - "
: e.vrfcCd === "NV" : e.vrfcCd === "NV"
? "일반검증 - " ? "일반검증 - "
: "HB검증 - "; : "모집단 검증 - ";
return `${prefix}${e.rsnExpln} return `${prefix}${e.rsnExpln}
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; (검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
@@ -539,9 +574,8 @@ function buildSewageExcretaRelatedSearchParam(
): SewageExcretaRelatedRequestParams { ): SewageExcretaRelatedRequestParams {
return { return {
statsYr: values.year, statsYr: values.year,
sewerBzmnCd: values.bsnmCd,
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
...(overrides?.mode ? { mode: overrides.mode } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}),
}; };
} }
@@ -549,8 +583,9 @@ function buildSewageExcretaRelatedSearchParam(
function mapListToGridRows<T extends { verificationResult?: SewageExcretaRelatedVerificationResult }>( function mapListToGridRows<T extends { verificationResult?: SewageExcretaRelatedVerificationResult }>(
list: T[] | undefined, list: T[] | undefined,
ctpvNm: string, ctpvNm: string,
values?: SearchFormValues | null,
): GridRowWithVerification[] { ): GridRowWithVerification[] {
return (list ?? []).map((item) => { return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification; const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification;
if (item.verificationResult) { if (item.verificationResult) {
row.verificationResult = 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))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
// 하수도 통계 관리 > 12. 하수 및 분뇨관련산업 // 하수도 통계 관리 > 12. 하수 및 분뇨관련산업
const SewageExcretaRelatedInp = () => { const SewageExcretaRelatedInp = () => {
const { sidoList } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const isAdminInfo = useAdminStore((state) => state.isAdminInfo); const isAdminInfo = useAdminStore((state) => state.isAdminInfo);
@@ -602,7 +639,7 @@ const SewageExcretaRelatedInp = () => {
const [tabStatusInfoMap, setTabStatusInfoMap] = useState< const [tabStatusInfoMap, setTabStatusInfoMap] = useState<
Map<string, SewerageStatusInfo | null> Map<string, SewerageStatusInfo | null>
>(new Map()); >(new Map());
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
Map<string, TabToolbarSnapshot> Map<string, TabToolbarSnapshot>
>(new Map()); >(new Map());
const [columnGuidesByTab, setColumnGuidesByTab] = useState< const [columnGuidesByTab, setColumnGuidesByTab] = useState<
@@ -612,6 +649,9 @@ const SewageExcretaRelatedInp = () => {
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>( const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(
null, null,
); );
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
() => new Map(),
);
const [hasSearched, setHasSearched] = useState(false); const [hasSearched, setHasSearched] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
@@ -620,6 +660,9 @@ const SewageExcretaRelatedInp = () => {
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
Map<string, boolean> Map<string, boolean>
>(new Map()); >(new Map());
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
Map<string, VerificationResultLikeMap | null>
>(new Map());
const [isNotApplicableByTab, setIsNotApplicableByTab] = useState<Map<string, boolean>>( const [isNotApplicableByTab, setIsNotApplicableByTab] = useState<Map<string, boolean>>(
() => new Map(), () => new Map(),
); );
@@ -645,36 +688,29 @@ const SewageExcretaRelatedInp = () => {
const sggOptionsSidoRef = useRef<string | null>(null); const sggOptionsSidoRef = useRef<string | null>(null);
activeTabRef.current = activeTab; activeTabRef.current = activeTab;
const activeTabId = useTabStore((s) => s.activeTabId);
useEffect(() => { 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; let cancelled = false;
void (async () => { void (async () => {
const entries = await Promise.all( const next = await fetchColumnGuidesRecord(statsCd);
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;
}
}),
);
if (cancelled) return; if (cancelled) return;
setColumnGuidesByTab(Object.fromEntries(entries)); setColumnGuidesByTab((prev) =>
Object.prototype.hasOwnProperty.call(prev, tabKey)
? prev
: { ...prev, [tabKey]: next },
);
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, [activeTabId, activeTab, columnGuidesByTab]);
const shouldShowNotApplicable = ( const shouldShowNotApplicable = (
tabKey: string, tabKey: string,
@@ -739,6 +775,13 @@ const SewageExcretaRelatedInp = () => {
); );
return next; return next;
}); });
if (searchContext) {
setSearchContextByTab((prev) => {
const next = new Map(prev);
next.set(tabKey, searchContext);
return next;
});
}
}; };
const markTabLoaded = (tabKey: string) => { const markTabLoaded = (tabKey: string) => {
@@ -854,6 +897,7 @@ const SewageExcretaRelatedInp = () => {
const gridRows = mapListToGridRows( const gridRows = mapListToGridRows(
publicInstitutionSewerIndustryList, publicInstitutionSewerIndustryList,
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
syncPublicInstitutionArticleTracking( syncPublicInstitutionArticleTracking(
tabKey, tabKey,
@@ -871,6 +915,9 @@ const SewageExcretaRelatedInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab( commitToolbarSnapshotForLoadedTab(
tabKey, tabKey,
@@ -897,6 +944,7 @@ const SewageExcretaRelatedInp = () => {
const gridRows = mapListToGridRows( const gridRows = mapListToGridRows(
publicInstitutionExcretaIndustryList, publicInstitutionExcretaIndustryList,
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
syncPublicInstitutionArticleTracking( syncPublicInstitutionArticleTracking(
tabKey, tabKey,
@@ -914,6 +962,9 @@ const SewageExcretaRelatedInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab( commitToolbarSnapshotForLoadedTab(
tabKey, tabKey,
@@ -936,7 +987,7 @@ const SewageExcretaRelatedInp = () => {
return false; return false;
} }
const { commonInfo, statusInfo, excretaRelatedIndustryList } = response.data; const { commonInfo, statusInfo, excretaRelatedIndustryList } = response.data;
const gridRows = mapListToGridRows(excretaRelatedIndustryList, commonInfo.ctpvNm); const gridRows = mapListToGridRows(excretaRelatedIndustryList, commonInfo.ctpvNm, searchContext);
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
m.set(ExcretaRelatedIndustryInpColumn.tabKey, gridRows); m.set(ExcretaRelatedIndustryInpColumn.tabKey, gridRows);
}); });
@@ -945,6 +996,9 @@ const SewageExcretaRelatedInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab( commitToolbarSnapshotForLoadedTab(
tabKey, tabKey,
@@ -970,6 +1024,7 @@ const SewageExcretaRelatedInp = () => {
const gridRows = mapListToGridRows( const gridRows = mapListToGridRows(
privateSewageTreatRelatedIndustryList, privateSewageTreatRelatedIndustryList,
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
m.set(PrivateSewageTreatRelatedIndustryInpColumn.tabKey, gridRows); m.set(PrivateSewageTreatRelatedIndustryInpColumn.tabKey, gridRows);
@@ -979,6 +1034,9 @@ const SewageExcretaRelatedInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab( commitToolbarSnapshotForLoadedTab(
tabKey, tabKey,
@@ -998,6 +1056,7 @@ const SewageExcretaRelatedInp = () => {
setRowDataMap(new Map()); setRowDataMap(new Map());
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setLastSearchedValues(null); setLastSearchedValues(null);
@@ -1010,6 +1069,7 @@ const SewageExcretaRelatedInp = () => {
setLoadedTabKeys(new Set()); setLoadedTabKeys(new Set());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setIsNotApplicableByTab(new Map()); setIsNotApplicableByTab(new Map());
setDeleteListByTab(new Map()); setDeleteListByTab(new Map());
clearArticleOgnzSnTracking( clearArticleOgnzSnTracking(
@@ -1060,7 +1120,9 @@ const SewageExcretaRelatedInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setIsNotApplicableByTab(new Map()); setIsNotApplicableByTab(new Map());
setDeleteListByTab(new Map()); setDeleteListByTab(new Map());
clearArticleOgnzSnTracking( clearArticleOgnzSnTracking(
@@ -1125,11 +1187,10 @@ const SewageExcretaRelatedInp = () => {
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey; const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey;
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: ctx.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido ?? "", ctpvNm: ctx.sido ?? "",
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year ?? "", statsYr: ctx.year ?? "",
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -1218,10 +1279,10 @@ const SewageExcretaRelatedInp = () => {
} }
commitActiveGrid(); 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 currentData = getTabGridRows(tabKey);
if (NOT_APPLICABLE_TAB_KEYS.has(tabKey)) { if (NOT_APPLICABLE_TAB_KEYS.has(tabKey)) {
if (!noApplicable && currentData.length === 0) { if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -1255,25 +1316,24 @@ const SewageExcretaRelatedInp = () => {
const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey); const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey);
const isPublicInstitutionTab = ARTICLE_EDIT_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 currentData = getTabGridRows(tabKey);
const mapRows = rowDataMap.get(tabKey) ?? []; const mapRows = rowDataMap.get(tabKey) ?? [];
const searchContextBase = { const searchContextBase = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
}; };
let submitList: let submitList:
| PublicInstitutionSewerIndustrySaveItem[] | PublicInstitutionSewerIndustrySaveItem[]
| PublicInstitutionExcretaIndustrySaveItem[]
| ExcretaRelatedIndustrySaveItem[] | ExcretaRelatedIndustrySaveItem[]
| PrivateSewageTreatRelatedIndustrySaveItem[]; | PrivateSewageTreatRelatedIndustrySaveItem[];
let deleteList: PublicInstitutionSewerIndustrySaveItem[] = []; let deleteList:
| PublicInstitutionSewerIndustrySaveItem[]
| PublicInstitutionExcretaIndustrySaveItem[] = [];
let sggList: Sgg[] = []; let sggList: Sgg[] = [];
if (isDistrictRowTab) { if (isDistrictRowTab) {
@@ -1305,7 +1365,7 @@ const SewageExcretaRelatedInp = () => {
sggList = list; sggList = list;
const rowsForSubmit = mergeGridRowsWithMapRows(currentData, mapRows, [ const rowsForSubmit = mergeGridRowsWithMapRows(currentData, mapRows, [
"ognzSn", PUBLIC_INSTITUTION_OGNZ_SN_FIELD,
"admdstCd", "admdstCd",
"admdstNm", "admdstNm",
"__isNewArticle", "__isNewArticle",
@@ -1327,7 +1387,7 @@ const SewageExcretaRelatedInp = () => {
) )
.map((row) => buildPublicInstitutionIndustrySaveItem(row, list)); .map((row) => buildPublicInstitutionIndustrySaveItem(row, list));
if (!noApplicable && submitList.length === 0) { if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -1341,14 +1401,14 @@ const SewageExcretaRelatedInp = () => {
const submitRequestBase = { const submitRequestBase = {
...searchContextBase, ...searchContextBase,
laePicNm: submitInfo.laePicNm, lclgvPicNm: submitInfo.lclgvPicNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laeIdfrNm: submitInfo.laeIdfrNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
...(isPublicInstitutionTab ? { noApplicable } : {}), ...(isPublicInstitutionTab ? { noApplicable } : {}),
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
}; };
@@ -1391,14 +1451,14 @@ const SewageExcretaRelatedInp = () => {
tabKey === ExcretaRelatedIndustryInpColumn.tabKey tabKey === ExcretaRelatedIndustryInpColumn.tabKey
? await statisticsInputSewageExcretaRelatedApi.submitExcretaRelatedIndustry({ ? await statisticsInputSewageExcretaRelatedApi.submitExcretaRelatedIndustry({
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: submitList as ExcretaRelatedIndustrySaveItem[], submitList: submitList as ExcretaRelatedIndustrySaveItem[],
}) })
: tabKey === PrivateSewageTreatRelatedIndustryInpColumn.tabKey : tabKey === PrivateSewageTreatRelatedIndustryInpColumn.tabKey
? await statisticsInputSewageExcretaRelatedApi.submitPrivateSewageTreatRelatedIndustry( ? await statisticsInputSewageExcretaRelatedApi.submitPrivateSewageTreatRelatedIndustry(
{ {
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: submitList:
submitList as PrivateSewageTreatRelatedIndustrySaveItem[], submitList as PrivateSewageTreatRelatedIndustrySaveItem[],
}, },
@@ -1407,17 +1467,17 @@ const SewageExcretaRelatedInp = () => {
? await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionSewerIndustry( ? await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionSewerIndustry(
{ {
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: submitList as PublicInstitutionSewerIndustrySaveItem[], submitList: submitList as PublicInstitutionSewerIndustrySaveItem[],
deleteList, deleteList: deleteList as PublicInstitutionSewerIndustrySaveItem[],
}, },
) )
: await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionExcretaIndustry( : await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionExcretaIndustry(
{ {
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: submitList as PublicInstitutionSewerIndustrySaveItem[], submitList: submitList as PublicInstitutionExcretaIndustrySaveItem[],
deleteList, deleteList: deleteList as PublicInstitutionExcretaIndustrySaveItem[],
}, },
); );
@@ -1431,15 +1491,16 @@ const SewageExcretaRelatedInp = () => {
if (finalStatus === "5") { if (finalStatus === "5") {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중..."); setGlobalProgressLabel("시계열 검증 진행 중...");
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
stats_cd: statsCdForTab, stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? "", stats_yr: lastSearchedValues.year ?? "",
lae_sbmsn_stts_cd: lae_sbmsn_stts_cd:
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2", activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
gen_verify_stts_cd: nvpassed ? "1" : "2", gen_verify_stts_cd: nvpassed ? "1" : "2",
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -1513,18 +1574,14 @@ const SewageExcretaRelatedInp = () => {
const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey); const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey);
const isPublicInstitutionTab = ARTICLE_EDIT_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 currentData = getTabGridRows(tabKey);
const mapRows = rowDataMap.get(tabKey) ?? []; const mapRows = rowDataMap.get(tabKey) ?? [];
const baseSaveRequest = { const baseSaveRequest = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
...(isPublicInstitutionTab && noApplicable ...(isPublicInstitutionTab && noApplicable
? { noApplicable: true as const } ? { noApplicable: true as const }
: {}), : {}),
@@ -1534,7 +1591,7 @@ const SewageExcretaRelatedInp = () => {
setIsLoading(true); setIsLoading(true);
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const userFields = { const userFields = {
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
}; };
@@ -1585,7 +1642,7 @@ const SewageExcretaRelatedInp = () => {
sggList = list; sggList = list;
const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [ const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [
"ognzSn", PUBLIC_INSTITUTION_OGNZ_SN_FIELD,
"admdstCd", "admdstCd",
"admdstNm", "admdstNm",
"__isNewArticle", "__isNewArticle",
@@ -1607,7 +1664,7 @@ const SewageExcretaRelatedInp = () => {
) )
.map((row) => buildPublicInstitutionIndustrySaveItem(row, list)); .map((row) => buildPublicInstitutionIndustrySaveItem(row, list));
if (!noApplicable && saveList.length === 0) { if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -1618,16 +1675,16 @@ const SewageExcretaRelatedInp = () => {
{ {
...baseSaveRequest, ...baseSaveRequest,
...userFields, ...userFields,
saveList, saveList: saveList as PublicInstitutionSewerIndustrySaveItem[],
deleteList, deleteList: deleteList as PublicInstitutionSewerIndustrySaveItem[],
}, },
) )
: await statisticsInputSewageExcretaRelatedApi.savePublicInstitutionExcretaIndustry( : await statisticsInputSewageExcretaRelatedApi.savePublicInstitutionExcretaIndustry(
{ {
...baseSaveRequest, ...baseSaveRequest,
...userFields, ...userFields,
saveList, saveList: saveList as PublicInstitutionExcretaIndustrySaveItem[],
deleteList, deleteList: deleteList as PublicInstitutionExcretaIndustrySaveItem[],
}, },
); );
} else { } else {
@@ -1698,8 +1755,8 @@ const SewageExcretaRelatedInp = () => {
newRow.__isNewArticle = 1; newRow.__isNewArticle = 1;
if (sggNm) { if (sggNm) {
const { admdstCd, admdstNm } = resolvePublicInstitutionAdmdst(newRow, list); const { admdstCd, admdstNm } = resolvePublicInstitutionAdmdst(newRow, list);
newRow.admdstCd = admdstCd; newRow.admdstCd = admdstCd ?? "";
newRow.admdstNm = admdstNm; newRow.admdstNm = admdstNm ?? "";
} }
flushSync(() => { flushSync(() => {
@@ -1816,10 +1873,10 @@ const SewageExcretaRelatedInp = () => {
for (const tab of SewageExcretaRelatedInpTabList) { for (const tab of SewageExcretaRelatedInpTabList) {
const tabKey = tab.tabKey; const tabKey = tab.tabKey;
const canEdit = const canEdit =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const rawColumns = (tab.columns ?? []) as ColumnConfig[]; const rawColumns = (tab.columns ?? []) as ColumnConfig[];
const base = canEdit const base = canEdit
? rawColumns ? rawColumns
@@ -1871,11 +1928,10 @@ const SewageExcretaRelatedInp = () => {
[publicInstitutionArticleCellStyleCallback], [publicInstitutionArticleCellStyleCallback],
); );
const searchContextForToolbar = lastSearchedValues ?? searchValues; const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab); const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab);
const activeTabToolbar = const activeTabToolbar =
toolbarSnapshotByTab.get(activeTab) ??
computeTabToolbarSnapshot( computeTabToolbarSnapshot(
activeTabStatus, activeTabStatus,
activeTabHasRowData, activeTabHasRowData,
@@ -1893,10 +1949,13 @@ const SewageExcretaRelatedInp = () => {
(activeTabToolbar.showActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab))); (activeTabToolbar.showActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab)));
const showArticleEditButtons = const showArticleEditButtons =
ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons; ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons;
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const showNotApplicableForActiveTab = const showNotApplicableForActiveTab =
NOT_APPLICABLE_TAB_KEYS.has(activeTab) && NOT_APPLICABLE_TAB_KEYS.has(activeTab) &&
loadedTabKeys.has(activeTab) && loadedTabKeys.has(activeTab) &&
shouldShowNotApplicable(activeTab, activeTabHasRowData, activeTabStatus); shouldShowNotApplicable(activeTab, activeTabHasRowData, activeTabStatus) &&
submitOk;
const showSecondaryToolbarCard = const showSecondaryToolbarCard =
hasSearched && hasSearched &&
toolbarGroupAllowed && toolbarGroupAllowed &&
@@ -1914,16 +1973,14 @@ const SewageExcretaRelatedInp = () => {
}, },
[isNotApplicableByTab, setIsNotApplicableForTab], [isNotApplicableByTab, setIsNotApplicableForTab],
); );
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
const verificationQueryContext = (() => { const verificationQueryContext = (() => {
const ctx = lastSearchedValues ?? searchValues; const ctx = lastSearchedValues ?? searchValues;
const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab; const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab;
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -1944,6 +2001,7 @@ const SewageExcretaRelatedInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
onSubmit={handleSearch} onSubmit={handleSearch}
@@ -1951,8 +2009,8 @@ const SewageExcretaRelatedInp = () => {
onValuesChange={setSearchValues} onValuesChange={setSearchValues}
/> />
{showActionToolbarCard && ( {showActionToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]"> <div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<VerificationResultButton <VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog} VerificationResultButtonFunc={openVerificationHistoryDialog}
isVerificationFailed={ isVerificationFailed={
@@ -1964,19 +2022,19 @@ const SewageExcretaRelatedInp = () => {
<LastYearDataButton <LastYearDataButton
getLastYearDataFunc={() => void lastYearDataFunc()} getLastYearDataFunc={() => void lastYearDataFunc()}
/> />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{activeTabStatus?.laeSbmsnSttsCd === "2" && ( {activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div>
</div> </div>
</div>
)} )}
{showSecondaryToolbarCard && ( {showSecondaryToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]"> <div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
{showNotApplicableForActiveTab && ( {showNotApplicableForActiveTab && (
<NotApplicableCheckButton <NotApplicableCheckButton
isCheck={isNotApplicableForActiveTab} isCheck={isNotApplicableForActiveTab}
@@ -1986,11 +2044,11 @@ const SewageExcretaRelatedInp = () => {
{showArticleEditButtons && ( {showArticleEditButtons && (
<> <>
<AddArticleButton addArticleFunc={() => void addArticleFunc()} /> <AddArticleButton addArticleFunc={() => void addArticleFunc()} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} /> <DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</> </>
)} )}
</div>
</div> </div>
</div>
)} )}
{isLoading && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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 rowData = rowDataMap.get(tabKey) ?? [];
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
const tabEnterableForGrid = const tabEnterableForGrid =
computeTabEnterable(tabStatusInfo, inputPeriodEnterable) || (computeTabEnterable(tabStatusInfo, inputPeriodEnterable) ||
isAdminInfo; isAdminInfo) && inputPeriodEnterable;
return ( return (
<TabsContent key={tabKey} value={tabKey}> <TabsContent key={tabKey} value={tabKey}>
@@ -2060,6 +2118,9 @@ const SewageExcretaRelatedInp = () => {
} }
getGridRef={activeTab === tabKey ? getGridRef : undefined} getGridRef={activeTab === tabKey ? getGridRef : undefined}
cellStyleCallback={resolveGridCellStyleCallback(tabKey)} cellStyleCallback={resolveGridCellStyleCallback(tabKey)}
footerVerificationResult={
footerVerificationByTab.get(tabKey) ?? null
}
cellTooltipCallback={verificationCellTooltip} cellTooltipCallback={verificationCellTooltip}
/> />
</TabsContent> </TabsContent>
@@ -2107,7 +2168,7 @@ const SewageExcretaRelatedInp = () => {
false, false,
); );
}} }}
/> />
</> </>
); );
}; };
@@ -1,19 +1,56 @@
import { import {
type Dispatch,
type SetStateAction,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useState, useState,
type Dispatch,
type SetStateAction,
} from "react"; } 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 { flushSync } from "react-dom";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; 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 AddArticleButton from "@/components/button/AddArticleButton";
import CancelSubmitButton from "@/components/button/CancelSubmitButton"; import CancelSubmitButton from "@/components/button/CancelSubmitButton";
@@ -24,12 +61,10 @@ import SaveButton from "@/components/button/SaveButton";
import SubmitButton from "@/components/button/SumitButton"; import SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton"; import VerificationResultButton from "@/components/button/VerificationResultButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results"; import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import SubmitDailog from "./SubmitDailog";
import type { import type {
CellEditedCallback, CellEditedCallback,
CellStyleCallback, CellStyleCallback,
@@ -41,42 +76,23 @@ import type {
} from "@/components/type/DataGrid.type"; } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { import {
DigestorOperatingStatusInpColumn,
DIGESTOR_OPERATING_STATUS_EN_YN_FIELD, DIGESTOR_OPERATING_STATUS_EN_YN_FIELD,
DigestorOperatingStatusInpColumn,
isDigestorOperatingRowDisabled, isDigestorOperatingRowDisabled,
} from "@/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn"; } from "@/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn";
import { ExcretaSlurgeProcessingStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/ExcretaSlurgeProcessingStatusInpColumn"; import { ExcretaSlurgeProcessingStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/ExcretaSlurgeProcessingStatusInpColumn";
import { SewageSlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn"; import { SewageSlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn";
import { SlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn"; 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 { toGridRows } from "@/components/util/grid/gridFunction";
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag"; import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
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 { import {
SEWAGE_EXCRETA_DIGESTOR_OPERATING_STATS_CD, patchFooterVerificationByTab,
SEWAGE_EXCRETA_EXCRETA_SLURGE_PROCESSING_STATS_CD, patchVerificationFailedByTab,
SEWAGE_EXCRETA_SEWAGE_SLURGE_FAC_STATUS_STATS_CD, } from "@/components/util/input/verification-failed-flag";
SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD, import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
} 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";
import { useAdminStore } from "@/store/admin-store"; 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> & { type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: SewageExcretaSlurgeVerificationResult; verificationResult?: SewageExcretaSlurgeVerificationResult;
@@ -136,12 +152,12 @@ function verificationCellTooltip({
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" e.vrfcCd === "IMR"
? "IMR검증 - " ? "시계열 검증 - "
: e.vrfcCd === "NV" : e.vrfcCd === "NV"
? "일반검증 - " ? "일반검증 - "
: "HB검증 - "; : "모집단 검증 - ";
return `${prefix}${e.rsnExpln} return `${prefix}${e.rsnExpln}
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; (검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
@@ -154,9 +170,8 @@ function buildSewageExcretaSlurgeSearchParam(
): SewageExcretaSlurgeRequestParams { ): SewageExcretaSlurgeRequestParams {
return { return {
statsYr: values.year, statsYr: values.year,
sewerBzmnCd: values.bsnmCd,
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
fcltNm: values.fctName?.trim() || undefined, fcltNm: values.fctName?.trim() || undefined,
...(overrides?.mode ? { mode: overrides.mode } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}),
}; };
@@ -165,8 +180,9 @@ function buildSewageExcretaSlurgeSearchParam(
function mapListToGridRows<T extends { verificationResult?: SewageExcretaSlurgeVerificationResult }>( function mapListToGridRows<T extends { verificationResult?: SewageExcretaSlurgeVerificationResult }>(
list: T[] | undefined, list: T[] | undefined,
ctpvNm: string, ctpvNm: string,
values?: SearchFormValues | null,
): GridRowWithVerification[] { ): GridRowWithVerification[] {
return (list ?? []).map((item) => { return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification; const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification;
if (item.verificationResult) { if (item.verificationResult) {
row.verificationResult = item.verificationResult; row.verificationResult = item.verificationResult;
@@ -175,10 +191,10 @@ function mapListToGridRows<T extends { verificationResult?: SewageExcretaSlurgeV
}); });
} }
const SewageExcretaSlurgeInpTabList = [ const SewageExcretaSlurgeInpTabList = [
SlurgeProcessingFacStatusInpColumn, SlurgeProcessingFacStatusInpColumn,
DigestorOperatingStatusInpColumn, DigestorOperatingStatusInpColumn,
SewageSlurgeProcessingFacStatusInpColumn, SewageSlurgeProcessingFacStatusInpColumn,
ExcretaSlurgeProcessingStatusInpColumn, ExcretaSlurgeProcessingStatusInpColumn,
] as const; ] as const;
@@ -327,8 +343,8 @@ const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -336,10 +352,22 @@ const computeTabEnterable = (
return periodEnterable; return periodEnterable;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || "") return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== "ALL") }
: true;
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( function computeTabToolbarSnapshot(
@@ -352,8 +380,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = const showSubmittedInInputPeriodToolbar =
isSubmitted && periodEnterable && gugunOk; 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 { function toGridNumber(value: string | number | null | undefined): number {
const n = Number(value); const n = Number(value);
return Number.isFinite(n) ? n : 0; return Number.isFinite(n) ? n : 0;
@@ -448,7 +469,7 @@ function toGridNumber(value: string | number | null | undefined): number {
function resolveSlurgeFacStatusAdmdst( function resolveSlurgeFacStatusAdmdst(
row: Record<string, string | number>, row: Record<string, string | number>,
sggOptions: Sgg[], sggOptions: Sgg[],
): { admdstCd: string; admdstNm: string } { ): { admdstCd: string | null; admdstNm: string | null } {
const sggNm = String(row.sggNm ?? "").trim(); const sggNm = String(row.sggNm ?? "").trim();
if (sggNm) { if (sggNm) {
const matched = sggOptions.find((s) => s.gugun === sggNm); const matched = sggOptions.find((s) => s.gugun === sggNm);
@@ -456,12 +477,15 @@ function resolveSlurgeFacStatusAdmdst(
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun }; return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
} }
} }
const existingCd = String(row.admdstCd ?? "").trim(); const existingCd = nullableTrimmedString(row.admdstCd);
const existingNm = String(row.admdstNm ?? "").trim(); const existingNm = nullableTrimmedString(row.admdstNm);
if (existingCd || existingNm) { 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 항목 */ /** 그리드 행 → ST00110001 저장/삭제 API 항목 */
@@ -473,46 +497,46 @@ function buildSlurgeProcessingFacStatusSaveItem(
return { return {
admdstCd, admdstCd,
admdstNm, admdstNm,
swtrmtFcltCd: String(row.swtrmtFcltCd ?? "").trim(), swtrmtFcltCd: nullableTrimmedString(row.swtrmtFcltCd),
swtrmtFcltNm: String(row.swtrmtFcltNm ?? "").trim(), swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
fcltSn: String(row.fcltSn ?? "").trim(), fcltSn: nullableTrimmedString(row.fcltSn),
itslfQnt: toGridNumber(row.itslfQnt), itslfQnt: toGridNumber(row.itslfQnt),
otsdInflwQnt: toGridNumber(row.otsdInflwQnt), otsdInflwQnt: toGridNumber(row.otsdInflwQnt),
transfTmplntNm: String(row.transfTmplntNm ?? "").trim(), transfTmplntNm: nullableTrimmedString(row.transfTmplntNm),
transfPrcsQnt: toGridNumber(row.transfPrcsQnt), transfPrcsQnt: toGridNumber(row.transfPrcsQnt),
srpFncRt: toGridNumber(row.srpFncRt), sludgeFncRt: toGridNumber(row.sludgeFncRt),
itslfTs1RclmQnt: toGridNumber(row.itslfTs1RclmQnt), itslfFrlvlRclmQnt: toGridNumber(row.itslfFrlvlRclmQnt),
itslfTs1IncnrQnt: toGridNumber(row.itslfTs1IncnrQnt), itslfFrlvlIncnrQnt: toGridNumber(row.itslfFrlvlIncnrQnt),
itslfTs1AltdQnt: toGridNumber(row.itslfTs1AltdQnt), itslfFrlvlSldQnt: toGridNumber(row.itslfFrlvlSldQnt),
itslfTs1DryQnt: toGridNumber(row.itslfTs1DryQnt), itslfFrlvlDryQnt: toGridNumber(row.itslfFrlvlDryQnt),
itslfTs1FuelQnt: toGridNumber(row.itslfTs1FuelQnt), itslfFrlvlCrbnQnt: toGridNumber(row.itslfFrlvlCrbnQnt),
itslfTs2CmpstgQnt: toGridNumber(row.itslfTs2CmpstgQnt), itslfFrlvlCmpstgQnt: toGridNumber(row.itslfFrlvlCmpstgQnt),
cmpsPrcsFcltBzentyNm: String(row.cmpsPrcsFcltBzentyNm ?? "").trim(), cmpsPrcsFcltBzentyNm: nullableTrimmedString(row.cmpsPrcsFcltBzentyNm),
incnrPrcsLfvlm: toGridNumber(row.incnrPrcsLfvlm), incnrPrcsLfvlm: toGridNumber(row.incnrPrcsLfvlm),
incnrPrcsPrdctRmtrlQnt: toGridNumber(row.incnrPrcsPrdctRmtrlQnt), incnrPrcsPrdctRmtrlQnt: toGridNumber(row.incnrPrcsPrdctRmtrlQnt),
incnrPrcsFmlyrQnt: toGridNumber(row.incnrPrcsFmlyrQnt), incnrPrcsCwthQnt: toGridNumber(row.incnrPrcsCwthQnt),
incnrPrcsFcltNm: String(row.incnrPrcsFcltNm ?? "").trim(), incnrPrcsFcltNm: nullableTrimmedString(row.incnrPrcsFcltNm),
stbznPrcsLfvlm: toGridNumber(row.stbznPrcsLfvlm), sldPrcsLfvlm: toGridNumber(row.sldPrcsLfvlm),
stbznPrcsFmlyrQnt: toGridNumber(row.stbznPrcsFmlyrQnt), sldPrcsCwthQnt: toGridNumber(row.sldPrcsCwthQnt),
stbznPrcsFcltNm: String(row.stbznPrcsFcltNm ?? "").trim(), sldPrcsFcltNm: nullableTrimmedString(row.sldPrcsFcltNm),
dryPrcsLfvlm: toGridNumber(row.dryPrcsLfvlm), dryPrcsLfvlm: toGridNumber(row.dryPrcsLfvlm),
dryPrcsIncnrQnt: toGridNumber(row.dryPrcsIncnrQnt), dryPrcsIncnrQnt: toGridNumber(row.dryPrcsIncnrQnt),
dryPrcsFuelQnt: toGridNumber(row.dryPrcsFuelQnt), dryPrcsFuelQnt: toGridNumber(row.dryPrcsFuelQnt),
dryPrcsPrdctRmtrlQnt: toGridNumber(row.dryPrcsPrdctRmtrlQnt), dryPrcsPrdctRmtrlQnt: toGridNumber(row.dryPrcsPrdctRmtrlQnt),
dryPrcsFmlyrQnt: toGridNumber(row.dryPrcsFmlyrQnt), dryPrcsCwthQnt: toGridNumber(row.dryPrcsCwthQnt),
dryPrcsCmpstgQnt: toGridNumber(row.dryPrcsCmpstgQnt), dryPrcsCmpstgQnt: toGridNumber(row.dryPrcsCmpstgQnt),
dryPrcsFcltNm: String(row.dryPrcsFcltNm ?? "").trim(), dryPrcsFcltNm: nullableTrimmedString(row.dryPrcsFcltNm),
fuelPrcsIncnrQnt: toGridNumber(row.fuelPrcsIncnrQnt), crbnPrcsIncnrQnt: toGridNumber(row.crbnPrcsIncnrQnt),
fuelPrcsFuelQnt: toGridNumber(row.fuelPrcsFuelQnt), crbnPrcsFuelQnt: toGridNumber(row.crbnPrcsFuelQnt),
fuelPrcsFcltNm: String(row.fuelPrcsFcltNm ?? "").trim(), crbnPrcsFcltNm: nullableTrimmedString(row.crbnPrcsFcltNm),
otsdCnsgnRclmQnt: toGridNumber(row.otsdCnsgnRclmQnt), otsdCnsgnRclmQnt: toGridNumber(row.otsdCnsgnRclmQnt),
otsdCnsgnIncnrQnt: toGridNumber(row.otsdCnsgnIncnrQnt), otsdCnsgnIncnrQnt: toGridNumber(row.otsdCnsgnIncnrQnt),
otsdCnsgnFuelPwplQnt: toGridNumber(row.otsdCnsgnFuelPwplQnt), otsdCnsgnPwplQnt: toGridNumber(row.otsdCnsgnPwplQnt),
otsdCnsgnPrdctRmtrlQnt: toGridNumber(row.otsdCnsgnPrdctRmtrlQnt), otsdCnsgnPrdctRmtrlQnt: toGridNumber(row.otsdCnsgnPrdctRmtrlQnt),
otsdCnsgnFmlyrQnt: toGridNumber(row.otsdCnsgnFmlyrQnt), otsdCnsgnCwthQnt: toGridNumber(row.otsdCnsgnCwthQnt),
otsdCnsgnCmpstgQnt: toGridNumber(row.otsdCnsgnCmpstgQnt), otsdCnsgnCmpstgQnt: toGridNumber(row.otsdCnsgnCmpstgQnt),
otsdCnsgnFcltNm: String(row.otsdCnsgnFcltNm ?? "").trim(), otsdCnsgnFcltNm: nullableTrimmedString(row.otsdCnsgnFcltNm),
cparTs1Whol: toGridNumber(row.cparTs1Whol), cparFrlvlWholQnt: toGridNumber(row.cparFrlvlWholQnt),
rmrk: nullableTrimmedString(row.rmrk), rmrk: nullableTrimmedString(row.rmrk),
}; };
} }
@@ -526,26 +550,26 @@ function buildDigestorOperatingStatusSaveItem(
const base = { const base = {
admdstCd, admdstCd,
admdstNm, admdstNm,
swtrmtFcltCd: String(row.swtrmtFcltCd ?? "").trim(), swtrmtFcltCd: nullableTrimmedString(row.swtrmtFcltCd),
swtrmtFcltNm: String(row.swtrmtFcltNm ?? "").trim(), swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
fcltSn: String(row.fcltSn ?? "").trim(), fcltSn: nullableTrimmedString(row.fcltSn),
digstnEnYn: String(row.digstnEnYn ?? "").trim(), gasPrcsEnYn: nullableTrimmedString(row.gasPrcsEnYn),
}; };
if (isDigestorOperatingRowDisabled(row)) { if (isDigestorOperatingRowDisabled(row)) {
return { return {
...base, ...base,
digstnEnYn: "N", gasPrcsEnYn: "N",
digstnCpct: 0, gasPrcsCpct: 0,
actlDrvnCpct: 0, actlDrvnCpct: 0,
digstnGasInputQnt: 0, prcsGasInputQnt: 0,
digstnGasInputSldDnst: 0, prcsGasInputSldDnst: 0,
digstnTnkOprtngDayCnt: 0, prcsRepoOprtngDayCnt: 0,
ocrnGasQnt: 0, ocrnGasQnt: 0,
digstnGasUtztnNtslQnt: 0, prcsGasUtztnNtslQnt: 0,
digstnGasUtztnEtgtQnt: 0, prcsGasUtztnEtgtQnt: 0,
digstnGasUtztnItslfQnt: 0, prcsGasUtztnItslfQnt: 0,
digstnGasUtztnEtcQnt: 0, prcsGasUtztnEtcQnt: 0,
surplsGasQnt: 0, surplsGasQnt: 0,
rmrk: null, rmrk: null,
}; };
@@ -553,16 +577,16 @@ function buildDigestorOperatingStatusSaveItem(
return { return {
...base, ...base,
digstnCpct: toGridNumber(row.digstnCpct), gasPrcsCpct: toGridNumber(row.gasPrcsCpct),
actlDrvnCpct: toGridNumber(row.actlDrvnCpct), actlDrvnCpct: toGridNumber(row.actlDrvnCpct),
digstnGasInputQnt: toGridNumber(row.digstnGasInputQnt), prcsGasInputQnt: toGridNumber(row.prcsGasInputQnt),
digstnGasInputSldDnst: toGridNumber(row.digstnGasInputSldDnst), prcsGasInputSldDnst: toGridNumber(row.prcsGasInputSldDnst),
digstnTnkOprtngDayCnt: toGridNumber(row.digstnTnkOprtngDayCnt), prcsRepoOprtngDayCnt: toGridNumber(row.prcsRepoOprtngDayCnt),
ocrnGasQnt: toGridNumber(row.ocrnGasQnt), ocrnGasQnt: toGridNumber(row.ocrnGasQnt),
digstnGasUtztnNtslQnt: toGridNumber(row.digstnGasUtztnNtslQnt), prcsGasUtztnNtslQnt: toGridNumber(row.prcsGasUtztnNtslQnt),
digstnGasUtztnEtgtQnt: toGridNumber(row.digstnGasUtztnEtgtQnt), prcsGasUtztnEtgtQnt: toGridNumber(row.prcsGasUtztnEtgtQnt),
digstnGasUtztnItslfQnt: toGridNumber(row.digstnGasUtztnItslfQnt), prcsGasUtztnItslfQnt: toGridNumber(row.prcsGasUtztnItslfQnt),
digstnGasUtztnEtcQnt: toGridNumber(row.digstnGasUtztnEtcQnt), prcsGasUtztnEtcQnt: toGridNumber(row.prcsGasUtztnEtcQnt),
surplsGasQnt: toGridNumber(row.surplsGasQnt), surplsGasQnt: toGridNumber(row.surplsGasQnt),
rmrk: nullableTrimmedString(row.rmrk), rmrk: nullableTrimmedString(row.rmrk),
}; };
@@ -577,13 +601,13 @@ function buildSewageSlurgeProcessingFacStatusSaveItem(
return { return {
admdstCd, admdstCd,
admdstNm, admdstNm,
fcltSn: String(row.fcltSn ?? "").trim(), fcltSn: nullableTrimmedString(row.fcltSn),
fcltNm: String(row.fcltNm ?? "").trim(), fcltNm: nullableTrimmedString(row.fcltNm),
prcsMthExpln: String(row.prcsMthExpln ?? "").trim(), prcsMthExpln: nullableTrimmedString(row.prcsMthExpln),
fcltCpct: toGridNumber(row.fcltCpct), sludgeFcltCpct: toGridNumber(row.sludgeFcltCpct),
anlPrcsQnt: toGridNumber(row.anlPrcsQnt), anlPrcsQnt: toGridNumber(row.anlPrcsQnt),
dayAvgOprtngHr: toGridNumber(row.dayAvgOprtngHr), dyrgOprtngHr: toGridNumber(row.dyrgOprtngHr),
anlOprtngDayCnt: toGridNumber(row.anlOprtngDayCnt), fcltAnlOprtngDayCnt: toGridNumber(row.fcltAnlOprtngDayCnt),
rmrk: nullableTrimmedString(row.rmrk), rmrk: nullableTrimmedString(row.rmrk),
}; };
} }
@@ -597,17 +621,17 @@ function buildExcretaSlurgeProcessingStatusSaveItem(
return { return {
admdstCd, admdstCd,
admdstNm, admdstNm,
fcltSn: String(row.fcltSn ?? "").trim(), fcltSn: nullableTrimmedString(row.fcltSn),
fcltNm: String(row.fcltNm ?? "").trim(), fcltNm: nullableTrimmedString(row.fcltNm),
fcltPstnCn: String(row.fcltPstnCn ?? "").trim(), fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
rcycQnt: toGridNumber(row.rcycQnt), rcycQnt: toGridNumber(row.rcycQnt),
olandLfvlm: toGridNumber(row.olandLfvlm), olandLfvlm: toGridNumber(row.olandLfvlm),
incnrQnt: toGridNumber(row.incnrQnt), incnrQnt: toGridNumber(row.incnrQnt),
oceanInputQnt: toGridNumber(row.oceanInputQnt), oceanInputQnt: toGridNumber(row.oceanInputQnt),
fuelQnt: toGridNumber(row.fuelQnt), fuelQnt: toGridNumber(row.fuelQnt),
etcQnt: toGridNumber(row.etcQnt), etcQnt: toGridNumber(row.etcQnt),
nondspsQnt: toGridNumber(row.nondspsQnt), unprcsQnt: toGridNumber(row.unprcsQnt),
swtrmtFcltNm: String(row.swtrmtFcltNm ?? "").trim(), swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
rmrk: nullableTrimmedString(row.rmrk), rmrk: nullableTrimmedString(row.rmrk),
}; };
} }
@@ -630,15 +654,17 @@ async function waitForImrJobCompleted(
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
// 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리 // 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리
const SewageExcretaSlurgeInp = () => { const SewageExcretaSlurgeInp = () => {
const { sidoList } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
@@ -667,9 +693,15 @@ const SewageExcretaSlurgeInp = () => {
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
Map<string, boolean> Map<string, boolean>
>(new Map()); >(new Map());
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
Map<string, VerificationResultLikeMap | null>
>(new Map());
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
Map<string, TabToolbarSnapshot> Map<string, TabToolbarSnapshot>
>(new Map()); >(new Map());
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
() => new Map(),
);
const [sggOptions, setSggOptions] = useState<Sgg[]>([]); const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
const [loadedTabKeys, setLoadedTabKeys] = useState<Set<string>>(() => new Set()); const [loadedTabKeys, setLoadedTabKeys] = useState<Set<string>>(() => new Set());
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
@@ -700,36 +732,29 @@ const SewageExcretaSlurgeInp = () => {
const preYearArticleFcltSnRef = useRef<ArticleFcltSnSet>(new Set()); const preYearArticleFcltSnRef = useRef<ArticleFcltSnSet>(new Set());
activeTabRef.current = activeTab; activeTabRef.current = activeTab;
const activeTabId = useTabStore((s) => s.activeTabId);
useEffect(() => { 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; let cancelled = false;
void (async () => { void (async () => {
const entries = await Promise.all( const next = await fetchColumnGuidesRecord(statsCd);
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;
}
}),
);
if (cancelled) return; if (cancelled) return;
setColumnGuidesByTab(Object.fromEntries(entries)); setColumnGuidesByTab((prev) =>
Object.prototype.hasOwnProperty.call(prev, tabKey)
? prev
: { ...prev, [tabKey]: next },
);
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, [activeTabId, activeTab, columnGuidesByTab]);
const shouldShowNotApplicable = ( const shouldShowNotApplicable = (
hasRows: boolean, hasRows: boolean,
@@ -789,6 +814,13 @@ const SewageExcretaSlurgeInp = () => {
); );
return next; 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>[] => const getTabGridRows = (tabKey: string): Record<string, string | number>[] =>
@@ -922,7 +954,7 @@ const SewageExcretaSlurgeInp = () => {
return false; return false;
} }
const { commonInfo, statusInfo, slurgeProcessingFacStatusList } = response.data; const { commonInfo, statusInfo, slurgeProcessingFacStatusList } = response.data;
const gridRows = mapListToGridRows(slurgeProcessingFacStatusList, commonInfo.ctpvNm); const gridRows = mapListToGridRows(slurgeProcessingFacStatusList, commonInfo.ctpvNm, searchContext);
syncSlurgeFacStatusArticleFcltSnTracking(gridRows, fromPreYear); syncSlurgeFacStatusArticleFcltSnTracking(gridRows, fromPreYear);
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
m.set(SlurgeProcessingFacStatusInpColumn.tabKey, gridRows); m.set(SlurgeProcessingFacStatusInpColumn.tabKey, gridRows);
@@ -932,6 +964,9 @@ const SewageExcretaSlurgeInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab( commitToolbarSnapshotForLoadedTab(
tabKey, tabKey,
@@ -954,7 +989,7 @@ const SewageExcretaSlurgeInp = () => {
return false; return false;
} }
const { commonInfo, statusInfo, digestorOperatingStatusList } = response.data; const { commonInfo, statusInfo, digestorOperatingStatusList } = response.data;
const gridRows = mapListToGridRows(digestorOperatingStatusList, commonInfo.ctpvNm); const gridRows = mapListToGridRows(digestorOperatingStatusList, commonInfo.ctpvNm, searchContext);
if (!fromPreYear) { if (!fromPreYear) {
publicSewageFacilitiesAvailableRef.current = gridRows.length > 0; publicSewageFacilitiesAvailableRef.current = gridRows.length > 0;
if (gridRows.length === 0 && overrides?.alertIfDigestorEmpty) { if (gridRows.length === 0 && overrides?.alertIfDigestorEmpty) {
@@ -969,6 +1004,9 @@ const SewageExcretaSlurgeInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab( commitToolbarSnapshotForLoadedTab(
tabKey, tabKey,
@@ -995,6 +1033,7 @@ const SewageExcretaSlurgeInp = () => {
const gridRows = mapListToGridRows( const gridRows = mapListToGridRows(
sewageSlurgeProcessingFacStatusList, sewageSlurgeProcessingFacStatusList,
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
m.set(SewageSlurgeProcessingFacStatusInpColumn.tabKey, gridRows); m.set(SewageSlurgeProcessingFacStatusInpColumn.tabKey, gridRows);
@@ -1004,6 +1043,9 @@ const SewageExcretaSlurgeInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab( commitToolbarSnapshotForLoadedTab(
tabKey, tabKey,
@@ -1029,6 +1071,7 @@ const SewageExcretaSlurgeInp = () => {
const gridRows = mapListToGridRows( const gridRows = mapListToGridRows(
excretaSlurgeProcessingStatusList, excretaSlurgeProcessingStatusList,
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
if (!fromPreYear) { if (!fromPreYear) {
excretaTreatmentFacilitiesAvailableRef.current = gridRows.length > 0; excretaTreatmentFacilitiesAvailableRef.current = gridRows.length > 0;
@@ -1044,6 +1087,9 @@ const SewageExcretaSlurgeInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab( commitToolbarSnapshotForLoadedTab(
tabKey, tabKey,
@@ -1065,7 +1111,9 @@ const SewageExcretaSlurgeInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setLastSearchedValues(null); setLastSearchedValues(null);
@@ -1123,7 +1171,9 @@ const SewageExcretaSlurgeInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
loadedTabKeysRef.current.clear(); loadedTabKeysRef.current.clear();
setLoadedTabKeys(new Set()); setLoadedTabKeys(new Set());
setDeleteListByTab(new Map()); setDeleteListByTab(new Map());
@@ -1232,9 +1282,9 @@ const SewageExcretaSlurgeInp = () => {
} }
commitActiveGrid(); 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 currentData = getTabGridRows(tabKey);
if (!noApplicable && currentData.length === 0) { if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -1262,7 +1312,7 @@ const SewageExcretaSlurgeInp = () => {
commitActiveGrid(); 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 currentData = getTabGridRows(tabKey);
const mapRows = rowDataMap.get(tabKey) ?? []; const mapRows = rowDataMap.get(tabKey) ?? [];
@@ -1320,6 +1370,7 @@ const SewageExcretaSlurgeInp = () => {
if ( if (
!noApplicable && !noApplicable &&
buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues) &&
((isSlurgeFacTab && slurgeSubmitList.length === 0) || ((isSlurgeFacTab && slurgeSubmitList.length === 0) ||
(isDigestorTab && digestorSubmitList.length === 0) || (isDigestorTab && digestorSubmitList.length === 0) ||
(isSewageSlurgeFacTab && sewageSlurgeFacSubmitList.length === 0) || (isSewageSlurgeFacTab && sewageSlurgeFacSubmitList.length === 0) ||
@@ -1341,12 +1392,8 @@ const SewageExcretaSlurgeInp = () => {
: SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD); : SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD);
const verificationQuery = { const verificationQuery = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
statsCd: statsCdForSubmit, statsCd: statsCdForSubmit,
}; };
@@ -1375,20 +1422,16 @@ const SewageExcretaSlurgeInp = () => {
const submitRequestBase = { const submitRequestBase = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL" userUuid: "",
? "" lclgvPicNm: submitInfo.lclgvPicNm,
: (lastSearchedValues.gugun ?? ""), lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
userId: "", lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laePicNm: submitInfo.laePicNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
noApplicable, noApplicable,
}; };
@@ -1400,21 +1443,21 @@ const SewageExcretaSlurgeInp = () => {
const response = isSlurgeFacTab const response = isSlurgeFacTab
? await statisticsInputSewageExcretaSlurgeApi.submitSlurgeProcessingFacStatus({ ? await statisticsInputSewageExcretaSlurgeApi.submitSlurgeProcessingFacStatus({
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: slurgeSubmitList, submitList: slurgeSubmitList,
deleteList: slurgeDeleteList, deleteList: slurgeDeleteList,
}) })
: isDigestorTab : isDigestorTab
? await statisticsInputSewageExcretaSlurgeApi.submitDigestorOperatingStatus({ ? await statisticsInputSewageExcretaSlurgeApi.submitDigestorOperatingStatus({
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: digestorSubmitList, submitList: digestorSubmitList,
}) })
: isSewageSlurgeFacTab : isSewageSlurgeFacTab
? await statisticsInputSewageExcretaSlurgeApi.submitSewageSlurgeProcessingFacStatus( ? await statisticsInputSewageExcretaSlurgeApi.submitSewageSlurgeProcessingFacStatus(
{ {
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: sewageSlurgeFacSubmitList, submitList: sewageSlurgeFacSubmitList,
deleteList: sewageSlurgeFacDeleteList, deleteList: sewageSlurgeFacDeleteList,
}, },
@@ -1422,7 +1465,7 @@ const SewageExcretaSlurgeInp = () => {
: await statisticsInputSewageExcretaSlurgeApi.submitExcretaSlurgeProcessingStatus( : await statisticsInputSewageExcretaSlurgeApi.submitExcretaSlurgeProcessingStatus(
{ {
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: excretaSlurgeSubmitList, submitList: excretaSlurgeSubmitList,
}, },
); );
@@ -1437,15 +1480,16 @@ const SewageExcretaSlurgeInp = () => {
if (finalStatus === "5") { if (finalStatus === "5") {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중..."); setGlobalProgressLabel("시계열 검증 진행 중...");
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
stats_cd: statsCdForSubmit, stats_cd: statsCdForSubmit,
stats_yr: lastSearchedValues.year ?? "", stats_yr: lastSearchedValues.year ?? "",
lae_sbmsn_stts_cd: lae_sbmsn_stts_cd:
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2", activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
gen_verify_stts_cd: nvpassed ? "1" : "2", gen_verify_stts_cd: nvpassed ? "1" : "2",
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -1517,14 +1561,10 @@ const SewageExcretaSlurgeInp = () => {
} }
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -1570,7 +1610,7 @@ const SewageExcretaSlurgeInp = () => {
commitActiveGrid(); 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 currentData = getTabGridRows(tabKey);
const mapRows = rowDataMap.get(tabKey) ?? []; const mapRows = rowDataMap.get(tabKey) ?? [];
@@ -1588,11 +1628,7 @@ const SewageExcretaSlurgeInp = () => {
const baseSaveRequest = { const baseSaveRequest = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
...(noApplicable ? { noApplicable: true } : {}), ...(noApplicable ? { noApplicable: true } : {}),
}; };
@@ -1600,7 +1636,7 @@ const SewageExcretaSlurgeInp = () => {
setIsLoading(true); setIsLoading(true);
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const userFields = { const userFields = {
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
}; };
@@ -1623,7 +1659,7 @@ const SewageExcretaSlurgeInp = () => {
.filter((row) => isPersistedArticleRowForTab(tabKey, row)) .filter((row) => isPersistedArticleRowForTab(tabKey, row))
.map((row) => buildSlurgeProcessingFacStatusSaveItem(row, list)); .map((row) => buildSlurgeProcessingFacStatusSaveItem(row, list));
if (!noApplicable && saveList.length === 0) { if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -1650,7 +1686,7 @@ const SewageExcretaSlurgeInp = () => {
buildDigestorOperatingStatusSaveItem(row, list), buildDigestorOperatingStatusSaveItem(row, list),
); );
if (!noApplicable && saveList.length === 0) { if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -1676,7 +1712,7 @@ const SewageExcretaSlurgeInp = () => {
.filter((row) => isPersistedArticleRowForTab(tabKey, row)) .filter((row) => isPersistedArticleRowForTab(tabKey, row))
.map((row) => buildSewageSlurgeProcessingFacStatusSaveItem(row, list)); .map((row) => buildSewageSlurgeProcessingFacStatusSaveItem(row, list));
if (!noApplicable && saveList.length === 0) { if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -1703,7 +1739,7 @@ const SewageExcretaSlurgeInp = () => {
buildExcretaSlurgeProcessingStatusSaveItem(row, list), buildExcretaSlurgeProcessingStatusSaveItem(row, list),
); );
if (!noApplicable && saveList.length === 0) { if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -1881,10 +1917,10 @@ const SewageExcretaSlurgeInp = () => {
for (const tab of SewageExcretaSlurgeInpTabList) { for (const tab of SewageExcretaSlurgeInpTabList) {
const tabKey = tab.tabKey; const tabKey = tab.tabKey;
const canEdit = const canEdit =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const rawColumns = (tab.columns ?? []) as ColumnConfig[]; const rawColumns = (tab.columns ?? []) as ColumnConfig[];
const base = canEdit const base = canEdit
? rawColumns ? rawColumns
@@ -1898,7 +1934,7 @@ const SewageExcretaSlurgeInp = () => {
col.fieldName === "sggNm" col.fieldName === "sggNm"
) { ) {
next = buildSggNmColumn(col, canEdit, sggOptions); 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" }; next = { ...col, editable: false as const, styleName: "rg-cell-readonly" };
} }
const guide = columnGuidesByTab[tabKey]?.[col.fieldName]?.trim(); const guide = columnGuidesByTab[tabKey]?.[col.fieldName]?.trim();
@@ -1962,7 +1998,7 @@ const SewageExcretaSlurgeInp = () => {
[digestorOperatingCellStyleCallback], [digestorOperatingCellStyleCallback],
); );
const searchContextForToolbar = lastSearchedValues ?? searchValues; const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab); const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab);
const activeTabToolbar = computeTabToolbarSnapshot( const activeTabToolbar = computeTabToolbarSnapshot(
@@ -1983,6 +2019,7 @@ const SewageExcretaSlurgeInp = () => {
const showSaveInToolbar = baseShowSaveInToolbar && toolbarGroupAllowed; const showSaveInToolbar = baseShowSaveInToolbar && toolbarGroupAllowed;
const showEnterableToolbarButtons = const showEnterableToolbarButtons =
baseShowEnterableToolbarButtons && toolbarGroupAllowed; baseShowEnterableToolbarButtons && toolbarGroupAllowed;
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const showActionToolbarCard = const showActionToolbarCard =
toolbarGroupAllowed && toolbarGroupAllowed &&
(baseShowActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab))); (baseShowActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab)));
@@ -1990,7 +2027,8 @@ const SewageExcretaSlurgeInp = () => {
ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons; ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons;
const showNotApplicableForActiveTab = const showNotApplicableForActiveTab =
loadedTabKeys.has(activeTab) && loadedTabKeys.has(activeTab) &&
shouldShowNotApplicable(activeTabHasRowData, activeTabStatus); shouldShowNotApplicable(activeTabHasRowData, activeTabStatus) &&
submitOk;
const showSecondaryToolbarCard = const showSecondaryToolbarCard =
hasSearched && hasSearched &&
toolbarGroupAllowed && toolbarGroupAllowed &&
@@ -2024,9 +2062,8 @@ const SewageExcretaSlurgeInp = () => {
const statsCd = tabStatsCdMap.get(activeTab); const statsCd = tabStatsCdMap.get(activeTab);
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -2047,6 +2084,7 @@ const SewageExcretaSlurgeInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
fctName fctName
@@ -2055,8 +2093,8 @@ const SewageExcretaSlurgeInp = () => {
onValuesChange={setSearchValues} onValuesChange={setSearchValues}
/> />
{showActionToolbarCard && ( {showActionToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]"> <div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<VerificationResultButton <VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog} VerificationResultButtonFunc={openVerificationHistoryDialog}
isVerificationFailed={ isVerificationFailed={
@@ -2068,19 +2106,19 @@ const SewageExcretaSlurgeInp = () => {
<LastYearDataButton <LastYearDataButton
getLastYearDataFunc={() => void lastYearDataFunc()} getLastYearDataFunc={() => void lastYearDataFunc()}
/> />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{activeTabStatus?.laeSbmsnSttsCd === "2" && ( {activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div> </div>
</div> </div>
)} )}
{showSecondaryToolbarCard && ( {showSecondaryToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]"> <div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
{showNotApplicableForActiveTab && ( {showNotApplicableForActiveTab && (
<NotApplicableCheckButton <NotApplicableCheckButton
isCheck={isNotApplicableForActiveTab} isCheck={isNotApplicableForActiveTab}
@@ -2089,12 +2127,12 @@ const SewageExcretaSlurgeInp = () => {
)} )}
{showArticleEditButtons && ( {showArticleEditButtons && (
<> <>
<AddArticleButton addArticleFunc={addArticleFunc} /> <AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} /> <DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</> </>
)} )}
</div> </div>
</div> </div>
)} )}
{isLoading && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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 activeTab === tabKey ? getGridRef : undefined
} }
cellStyleCallback={resolveGridCellStyleCallback(tabKey)} cellStyleCallback={resolveGridCellStyleCallback(tabKey)}
footerVerificationResult={
footerVerificationByTab.get(tabKey) ?? null
}
cellTooltipCallback={verificationCellTooltip} cellTooltipCallback={verificationCellTooltip}
onCellEdited={ onCellEdited={
tabKey === DigestorOperatingStatusInpColumn.tabKey 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 { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { import {
STATISTICS_INPUT_URL_PATH, STATISTICS_INPUT_URL_PATH,
verificationSearchToFormValues, verificationSearchToFormValues,
} from "@/shared/lib/stats-cd-input-navigation"; } 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 { 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 { sewagePipeMaintainApiService } from "@/entities/statistics/api/input/statistics-input-sewage-pipe-maintain-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
@@ -27,7 +27,15 @@ import type {
cancelSubmitRequest, cancelSubmitRequest,
ImrStatusResponse, ImrStatusResponse,
SewerageStatusInfo, SewerageStatusInfo,
Sido,
} from "@/entities/statistics/model/statistics-interface"; } from "@/entities/statistics/model/statistics-interface";
import {
filterBySelectedAdmdstCd,
isGugunOkForToolbar,
isSubmitOkForToolbar,
toInputSearchRegion,
} from "@/entities/statistics";
import { import {
LoadingWithAnimation, LoadingWithAnimation,
Tabs, Tabs,
@@ -46,18 +54,23 @@ import SaveButton from "@/components/button/SaveButton";
import SubmitButton from "@/components/button/SumitButton"; import SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton"; import VerificationResultButton from "@/components/button/VerificationResultButton";
import DataGrid from "@/components/module/DataGrid"; 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 { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import type { ColumnInfo } from "@/components/type/DataGrid.type"; import type { ColumnInfo } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.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 { PipeDredgingStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/PipeDredgingStatusInpColumn";
import { RepairStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/RepairStatusInpColumn"; import { RepairStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/RepairStatusInpColumn";
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
import { toGridRows } from "@/components/util/grid/gridFunction"; 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 { useAdminStore } from "@/store/admin-store";
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store"; import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
import { useTabStore } from "@/store/tab-store"; import { useTabStore } from "@/store/tab-store";
@@ -68,9 +81,8 @@ function buildSewagePipeMaintainSearchParam(
): SewagePipeMaintainRequestParams { ): SewagePipeMaintainRequestParams {
return { return {
statsYr: values.year, statsYr: values.year,
sewerBzmnCd: values.bsnmCd,
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
...(overrides?.mode ? { mode: overrides.mode } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}),
}; };
} }
@@ -82,12 +94,6 @@ const SewagePipeMaintainInpTabList = [
const firstTabKey = RepairStatusInpColumn.tabKey; 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 => ({ const toGridColumnInfo = (tab: (typeof SewagePipeMaintainInpTabList)[number]): ColumnInfo => ({
columns: tab.columns ?? [], columns: tab.columns ?? [],
columnLayout: tab.columnLayout ?? [], columnLayout: tab.columnLayout ?? [],
@@ -107,8 +113,8 @@ const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -116,10 +122,14 @@ const computeTabEnterable = (
return periodEnterable; return periodEnterable;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || "") return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== "ALL") }
: true;
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
return isSubmitOkForToolbar(sidoListRef.current, values);
} }
function computeTabToolbarSnapshot( function computeTabToolbarSnapshot(
@@ -132,8 +142,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = const showSubmittedInInputPeriodToolbar =
isSubmitted && periodEnterable && gugunOk; isSubmitted && periodEnterable && gugunOk;
@@ -154,17 +164,6 @@ function tabRowDataExists(
return Array.isArray(rows) && rows.length > 0; 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)); const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
async function waitForImrJobCompleted( async function waitForImrJobCompleted(
@@ -183,11 +182,11 @@ async function waitForImrJobCompleted(
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
function buildColumnFormatNumber( function buildColumnFormatNumber(
@@ -218,25 +217,26 @@ function buildRepairRowList(
const formatNumber = buildColumnFormatNumber(RepairStatusInpColumn.columns); const formatNumber = buildColumnFormatNumber(RepairStatusInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm), sggNm: nullableTrimmedString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
mendngWhol: formatNumber(d.mendngWhol, "mendngWhol"), dfrpWholPrlg: formatNumber(d.dfrpWholPrlg, "dfrpWholPrlg"),
cbsysMendngPrlg: formatNumber(d.cbsysMendngPrlg, "cbsysMendngPrlg"), cbsysDfrpPrlg: formatNumber(d.cbsysDfrpPrlg, "cbsysDfrpPrlg"),
sprsysExlnPrlg: formatNumber(d.sprsysExlnPrlg, "sprsysExlnPrlg"), sprsysExlnPrlg: formatNumber(d.sprsysExlnPrlg, "sprsysExlnPrlg"),
sprsysDrtwPrlg: formatNumber(d.sprsysDrtwPrlg, "sprsysDrtwPrlg"), sprsysDrtwPrlg: formatNumber(d.sprsysDrtwPrlg, "sprsysDrtwPrlg"),
cbsysPartMendngPrlg: formatNumber(d.cbsysPartMendngPrlg, "cbsysPartMendngPrlg"), partDfrpWhol: formatNumber(d.partDfrpWhol, "partDfrpWhol"),
sprsysExlnMendngPrlg: formatNumber(d.sprsysExlnMendngPrlg, "sprsysExlnMendngPrlg"), cbsysPartDfrpPrlg: formatNumber(d.cbsysPartDfrpPrlg, "cbsysPartDfrpPrlg"),
sprsysDrtwMendngPrlg: formatNumber(d.sprsysDrtwMendngPrlg, "sprsysDrtwMendngPrlg"), sprsysExlnDfrpPrlg: formatNumber(d.sprsysExlnDfrpPrlg, "sprsysExlnDfrpPrlg"),
sprsysDrtwDfrpPrlg: formatNumber(d.sprsysDrtwDfrpPrlg, "sprsysDrtwDfrpPrlg"),
cbsysMnhlCnt: formatNumber(d.cbsysMnhlCnt, "cbsysMnhlCnt"), cbsysMnhlCnt: formatNumber(d.cbsysMnhlCnt, "cbsysMnhlCnt"),
sprsysDrtwMnhlCnt: formatNumber(d.sprsysDrtwMnhlCnt, "sprsysDrtwMnhlCnt"), sprsysDrtwMnhlCnt: formatNumber(d.sprsysDrtwMnhlCnt, "sprsysDrtwMnhlCnt"),
sprsysExlnMnhlCnt: formatNumber(d.sprsysExlnMnhlCnt, "sprsysExlnMnhlCnt"), sprsysExlnMnhlCnt: formatNumber(d.sprsysExlnMnhlCnt, "sprsysExlnMnhlCnt"),
drtwRcvprpCnt: formatNumber(d.drtwRcvprpCnt, "drtwRcvprpCnt"), drtwDrgCnt: formatNumber(d.drtwDrgCnt, "drtwDrgCnt"),
exlnRcvprpCnt: formatNumber(d.exlnRcvprpCnt, "exlnRcvprpCnt"), exlnDrgCnt: formatNumber(d.exlnDrgCnt, "exlnDrgCnt"),
stofcbrCnt: formatNumber(d.stofcbrCnt, "stofcbrCnt"), stofcbrCnt: formatNumber(d.stofcbrCnt, "stofcbrCnt"),
otflCnt: formatNumber(d.otflCnt, "otflCnt"), otflCnt: formatNumber(d.otflCnt, "otflCnt"),
cctvExmnPrlg: formatNumber(d.cctvExmnPrlg, "cctvExmnPrlg"), cctvExmnPrlg: formatNumber(d.cctvExmnPrlg, "cctvExmnPrlg"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
})); }));
} }
@@ -246,34 +246,36 @@ function buildDredgingRowList(
const formatNumber = buildColumnFormatNumber(PipeDredgingStatusInpColumn.columns); const formatNumber = buildColumnFormatNumber(PipeDredgingStatusInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm), sggNm: nullableTrimmedString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"), wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"),
wholSwagePpl: formatNumber(d.wholSwagePpl, "wholSwagePpl"), wholSwagePpl: formatNumber(d.wholSwagePpl, "wholSwagePpl"),
cbsysPplPrlg: formatNumber(d.cbsysPplPrlg, "cbsysPplPrlg"), cbsysPplPrlg: formatNumber(d.cbsysPplPrlg, "cbsysPplPrlg"),
sprsysExlnPrlg: formatNumber(d.sprsysExlnPrlg, "sprsysExlnPrlg"), sprsysExlnPrlg: formatNumber(d.sprsysExlnPrlg, "sprsysExlnPrlg"),
sprsysDrtwPrlg: formatNumber(d.sprsysDrtwPrlg, "sprsysDrtwPrlg"), sprsysDrtwPrlg: formatNumber(d.sprsysDrtwPrlg, "sprsysDrtwPrlg"),
mnhlCnt: formatNumber(d.mnhlCnt, "mnhlCnt"), mnhlCnt: formatNumber(d.mnhlCnt, "mnhlCnt"),
exlnDrtwRcvprpCnt: formatNumber(d.exlnDrtwRcvprpCnt, "exlnDrtwRcvprpCnt"), exlnDrtwDrgCnt: formatNumber(d.exlnDrtwDrgCnt, "exlnDrtwDrgCnt"),
stofcbrOtflCnt: formatNumber(d.stofcbrOtflCnt, "stofcbrOtflCnt"), stofcbrOtflCnt: formatNumber(d.stofcbrOtflCnt, "stofcbrOtflCnt"),
drdgnPlanPrlg: formatNumber(d.drdgnPlanPrlg, "drdgnPlanPrlg"), drdgnPlanPrlg: formatNumber(d.drdgnPlanPrlg, "drdgnPlanPrlg"),
drdgnTrgtCtrsDrdgnRt: formatNumber(d.drdgnTrgtCtrsDrdgnRt, "drdgnTrgtCtrsDrdgnRt"), drdgnTrgtCtrsDrdgnRt: formatNumber(d.drdgnTrgtCtrsDrdgnRt, "drdgnTrgtCtrsDrdgnRt"),
wholPrlgCtrsDrdgnRt: formatNumber(d.wholPrlgCtrsDrdgnRt, "wholPrlgCtrsDrdgnRt"), wholPrlgCtrsDrdgnRt: formatNumber(d.wholPrlgCtrsDrdgnRt, "wholPrlgCtrsDrdgnRt"),
drdgnAmt: formatNumber(d.drdgnAmt, "drdgnAmt"), drdgnAmt: formatNumber(d.drdgnAmt, "drdgnAmt"),
drdgnQnt: formatNumber(d.drdgnQnt, "drdgnQnt"), pplDrdqty: formatNumber(d.pplDrdqty, "pplDrdqty"),
drdgnUntprcAmt: formatNumber(d.drdgnUntprcAmt, "drdgnUntprcAmt"), drdgnSoilPrcsAmt: formatNumber(d.drdgnSoilPrcsAmt, "drdgnSoilPrcsAmt"),
wholDrdgnPrcsQnt: formatNumber(d.wholDrdgnPrcsQnt, "wholDrdgnPrcsQnt"), wholDrdgnPrcsQnt: formatNumber(d.wholDrdgnPrcsQnt, "wholDrdgnPrcsQnt"),
rclmQnt: formatNumber(d.rclmQnt, "rclmQnt"), rclmQnt: formatNumber(d.rclmQnt, "rclmQnt"),
oceanEmsnQnt: formatNumber(d.oceanEmsnQnt, "oceanEmsnQnt"), oceanEmsnQnt: formatNumber(d.oceanEmsnQnt, "oceanEmsnQnt"),
rcycQnt: formatNumber(d.rcycQnt, "rcycQnt"), rcycQnt: formatNumber(d.rcycQnt, "rcycQnt"),
etcQnt: formatNumber(d.etcQnt, "etcQnt"), etcQnt: formatNumber(d.etcQnt, "etcQnt"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
})); }));
} }
// 하수도 통계 관리 > 03. 하수관로 유지관리 // 하수도 통계 관리 > 03. 하수관로 유지관리
const SewagePipeMaintainInp = () => { const SewagePipeMaintainInp = () => {
const { sidoList, isReady } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
const toolbarGroupAllowed = useStatisticsInputToolbarGroupGate(); const toolbarGroupAllowed = useStatisticsInputToolbarGroupGate();
@@ -296,9 +298,15 @@ const SewagePipeMaintainInp = () => {
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
Map<string, boolean> Map<string, boolean>
>(new Map()); >(new Map());
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< const [footerVerificationByTab, setFooterVerificationByTab] = useState<
Map<string, VerificationResultLikeMap | null>
>(new Map());
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
Map<string, TabToolbarSnapshot> Map<string, TabToolbarSnapshot>
>(new Map()); >(new Map());
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
() => new Map(),
);
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false); const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
const [showSubmitDialog, setShowSubmitDialog] = useState(false); const [showSubmitDialog, setShowSubmitDialog] = useState(false);
@@ -328,6 +336,7 @@ const SewagePipeMaintainInp = () => {
useLayoutEffect(() => { useLayoutEffect(() => {
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN) return; if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN) return;
if (!isReady) return;
const pending = useStatisticsInputFocusStore const pending = useStatisticsInputFocusStore
.getState() .getState()
.takePendingForUrl(STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN); .takePendingForUrl(STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN);
@@ -335,43 +344,34 @@ const SewagePipeMaintainInp = () => {
const { tabKey, search } = pending; const { tabKey, search } = pending;
setActiveTab(tabKey); setActiveTab(tabKey);
if (search) { if (search) {
const values = verificationSearchToFormValues(search); const values = verificationSearchToFormValues(search, sidoList);
setVerificationSearchDefaults(values); setVerificationSearchDefaults(values);
postVerificationSearchRef.current = { values, tabKey }; postVerificationSearchRef.current = { values, tabKey };
setSearchFormRemountKey((k) => k + 1); setSearchFormRemountKey((k) => k + 1);
} }
}, [activeTabId, inputFocusNonce]); }, [activeTabId, inputFocusNonce, isReady, sidoList]);
useEffect(() => { useEffect(() => {
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN) return; 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; let cancelled = false;
void (async () => { void (async () => {
const entries = await Promise.all( const next = await fetchColumnGuidesRecord(statsCd);
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;
}
}),
);
if (cancelled) return; if (cancelled) return;
setColumnGuidesByTab(Object.fromEntries(entries)); setColumnGuidesByTab((prev) =>
Object.prototype.hasOwnProperty.call(prev, tabKey)
? prev
: { ...prev, [tabKey]: next },
);
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [activeTabId]); }, [activeTabId, activeTab, columnGuidesByTab]);
const commitToolbarSnapshotForLoadedTab = ( const commitToolbarSnapshotForLoadedTab = (
tabKey: string, tabKey: string,
@@ -394,6 +394,13 @@ const SewagePipeMaintainInp = () => {
); );
return next; return next;
}); });
if (searchContext) {
setSearchContextByTab((prev) => {
const next = new Map(prev);
next.set(tabKey, searchContext);
return next;
});
}
}; };
const applyRowMap = ( const applyRowMap = (
@@ -435,7 +442,11 @@ const SewagePipeMaintainInp = () => {
return false; return false;
} }
const { commonInfo, statusInfo, repairStatusList } = response.data; const { commonInfo, statusInfo, repairStatusList } = response.data;
const convertedData = (repairStatusList ?? []).map((d) => ({ const convertedData = filterBySelectedAdmdstCd(
repairStatusList ?? [],
sidoListRef.current,
searchContext,
).map((d) => ({
ctpvNm: commonInfo.ctpvNm, ctpvNm: commonInfo.ctpvNm,
...d, ...d,
})); }));
@@ -449,6 +460,9 @@ const SewagePipeMaintainInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) { if (updateLastSearchedValues) {
setLastSearchedValues(searchContext); setLastSearchedValues(searchContext);
} }
@@ -471,7 +485,11 @@ const SewagePipeMaintainInp = () => {
return false; return false;
} }
const { commonInfo, statusInfo, pipeDredgingStatusList } = response.data; const { commonInfo, statusInfo, pipeDredgingStatusList } = response.data;
const convertedData = (pipeDredgingStatusList ?? []).map((d) => ({ const convertedData = filterBySelectedAdmdstCd(
pipeDredgingStatusList ?? [],
sidoListRef.current,
searchContext,
).map((d) => ({
ctpvNm: commonInfo.ctpvNm, ctpvNm: commonInfo.ctpvNm,
...d, ...d,
})); }));
@@ -485,6 +503,9 @@ const SewagePipeMaintainInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) { if (updateLastSearchedValues) {
setLastSearchedValues(searchContext); setLastSearchedValues(searchContext);
} }
@@ -592,28 +613,20 @@ const SewagePipeMaintainInp = () => {
const submitInfoBase = { const submitInfoBase = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL" lclgvPicNm: submitInfo.lclgvPicNm,
? "" lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
: (lastSearchedValues.gugun ?? ""), lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
laePicNm: submitInfo.laePicNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
}; };
const verificationQuery = { const verificationQuery = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -636,12 +649,12 @@ const SewagePipeMaintainInp = () => {
const response = isRepairTab const response = isRepairTab
? await sewagePipeMaintainApiService.submitSewagePipeMaintainRepair({ ? await sewagePipeMaintainApiService.submitSewagePipeMaintainRepair({
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: buildRepairRowList(currentData), submitList: buildRepairRowList(currentData),
} satisfies SewagePipeMaintainRepairSubmitRequest) } satisfies SewagePipeMaintainRepairSubmitRequest)
: await sewagePipeMaintainApiService.submitSewagePipeMaintainDredging({ : await sewagePipeMaintainApiService.submitSewagePipeMaintainDredging({
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: buildDredgingRowList(currentData), submitList: buildDredgingRowList(currentData),
} satisfies SewagePipeMaintainDredgingSubmitRequest); } satisfies SewagePipeMaintainDredgingSubmitRequest);
@@ -655,14 +668,15 @@ const SewagePipeMaintainInp = () => {
if (finalStatus === "5") { if (finalStatus === "5") {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중..."); setGlobalProgressLabel("시계열 검증 진행 중...");
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
stats_cd: statsCdForTab, stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? "", 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", gen_verify_stts_cd: nvpassed ? "1" : "2",
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -738,11 +752,7 @@ const SewagePipeMaintainInp = () => {
const baseRequest = { const baseRequest = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
}; };
try { try {
@@ -758,7 +768,7 @@ const SewagePipeMaintainInp = () => {
const param: SewagePipeMaintainRepairSaveRequest = { const param: SewagePipeMaintainRepairSaveRequest = {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: buildRepairRowList(currentData), saveList: buildRepairRowList(currentData),
}; };
if (isAdminInfo) { if (isAdminInfo) {
@@ -795,7 +805,7 @@ const SewagePipeMaintainInp = () => {
const param: SewagePipeMaintainDredgingSaveRequest = { const param: SewagePipeMaintainDredgingSaveRequest = {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: buildDredgingRowList(currentData), saveList: buildDredgingRowList(currentData),
}; };
if (isAdminInfo) { if (isAdminInfo) {
@@ -844,14 +854,10 @@ const SewagePipeMaintainInp = () => {
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey; const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey;
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -880,7 +886,9 @@ const SewagePipeMaintainInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
setLastSearchedValues(null); setLastSearchedValues(null);
setHasSearched(false); setHasSearched(false);
@@ -925,7 +933,9 @@ const SewagePipeMaintainInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
} }
@@ -1000,10 +1010,10 @@ const SewagePipeMaintainInp = () => {
for (const tab of SewagePipeMaintainInpTabList) { for (const tab of SewagePipeMaintainInpTabList) {
const tabKey = tab.tabKey; const tabKey = tab.tabKey;
const canEdit = const canEdit =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const rawColumns = tab.columns ?? []; const rawColumns = tab.columns ?? [];
const base = canEdit const base = canEdit
? rawColumns ? rawColumns
@@ -1021,10 +1031,9 @@ const SewagePipeMaintainInp = () => {
return map; return map;
}, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]); }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]);
const searchContextForToolbar = lastSearchedValues ?? searchValues; const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const activeTabToolbar = const activeTabToolbar =
toolbarSnapshotByTab.get(activeTab) ??
computeTabToolbarSnapshot( computeTabToolbarSnapshot(
activeTabStatus, activeTabStatus,
tabRowDataExists(rowDataMap, activeTab), tabRowDataExists(rowDataMap, activeTab),
@@ -1045,6 +1054,7 @@ const SewagePipeMaintainInp = () => {
baseShowEnterableToolbarButtons && toolbarGroupAllowed; baseShowEnterableToolbarButtons && toolbarGroupAllowed;
const showActionToolbarCard = const showActionToolbarCard =
baseShowActionToolbarCard && toolbarGroupAllowed; baseShowActionToolbarCard && toolbarGroupAllowed;
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const openVerificationHistoryDialog = () => { const openVerificationHistoryDialog = () => {
const ctx = lastSearchedValues ?? searchValues; const ctx = lastSearchedValues ?? searchValues;
@@ -1061,9 +1071,8 @@ const SewagePipeMaintainInp = () => {
const statsCd = tabStatsCdMap.get(activeTab); const statsCd = tabStatsCdMap.get(activeTab);
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -1099,8 +1108,8 @@ const SewagePipeMaintainInp = () => {
return errors return errors
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" ? "IMR검증 - " : e.vrfcCd === "NV" ? "일반검증 - " : "HB검증 - "; e.vrfcCd === "IMR" ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - ";
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
@@ -1111,6 +1120,7 @@ const SewagePipeMaintainInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
onSubmit={handleSearch} onSubmit={handleSearch}
@@ -1131,10 +1141,10 @@ const SewagePipeMaintainInp = () => {
<LastYearDataButton <LastYearDataButton
getLastYearDataFunc={() => void getLastYearDataFunc()} getLastYearDataFunc={() => void getLastYearDataFunc()}
/> />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{activeTabStatus?.laeSbmsnSttsCd === "2" && ( {activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
@@ -1166,11 +1176,11 @@ const SewagePipeMaintainInp = () => {
const tabKey = info.tabKey; const tabKey = info.tabKey;
const rowData = rowDataMap.get(tabKey) ?? []; const rowData = rowDataMap.get(tabKey) ?? [];
const tabEnterableForGrid = const tabEnterableForGrid =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const gugunOk = buildGugunOkForToolbar(lastSearchedValues); const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
return ( return (
@@ -1182,6 +1192,7 @@ const SewagePipeMaintainInp = () => {
toGridColumnInfo(info) toGridColumnInfo(info)
} }
excelExportFileSuffix={info.tabLabel || tabKey} excelExportFileSuffix={info.tabLabel || tabKey}
fixedColumns={info.fixedColumns}
rowData={rowData} rowData={rowData}
setRowData={(updater) => { setRowData={(updater) => {
setRowDataMap((prev) => { setRowDataMap((prev) => {
@@ -1206,6 +1217,9 @@ const SewagePipeMaintainInp = () => {
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd} excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
cellStyleCallback={verificationCellStyle} cellStyleCallback={verificationCellStyle}
footerVerificationResult={
footerVerificationByTab.get(tabKey) ?? null
}
cellTooltipCallback={verificationCellTooltip} cellTooltipCallback={verificationCellTooltip}
getGridDataRef={ getGridDataRef={
activeTab === tabKey ? getGridDataRef : undefined activeTab === tabKey ? getGridDataRef : undefined
@@ -1,21 +1,59 @@
import { import {
type Dispatch,
type SetStateAction,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useState, useState,
type Dispatch,
type SetStateAction,
} from "react"; } from "react";
import { flushSync } from "react-dom"; import { flushSync } from "react-dom";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { ValueType } from "realgrid"; import { ValueType } from "realgrid";
import type { RealGridReact } from "realgrid-react"; 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 { 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 AddArticleButton from "@/components/button/AddArticleButton";
import CancelSubmitButton from "@/components/button/CancelSubmitButton"; import CancelSubmitButton from "@/components/button/CancelSubmitButton";
import DeleteArticleButton from "@/components/button/DeleteArticleButton"; import DeleteArticleButton from "@/components/button/DeleteArticleButton";
@@ -25,13 +63,10 @@ import SaveButton from "@/components/button/SaveButton";
import SubmitButton from "@/components/button/SumitButton"; import SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton"; import VerificationResultButton from "@/components/button/VerificationResultButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results"; import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import SubmitDailog from "./SubmitDailog";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import type { import type {
CellStyleCallbackParams, CellStyleCallbackParams,
CellTooltipCallbackParams, CellTooltipCallbackParams,
@@ -39,44 +74,24 @@ import type {
ColumnInfo, ColumnInfo,
ColumnInfoList, ColumnInfoList,
} from "@/components/type/DataGrid.type"; } 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 { RainfallPumpStationInpColumn } from "@/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn";
import { SewageReservoirInpColumn } from "@/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn"; import { SewageReservoirInpColumn } from "@/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn";
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
import { import {
formatCompactYmdForGrid, formatCompactYmdForGrid,
normalizeOxYnValue, normalizeOxYnValue,
toCompactYmdOrNull, toCompactYmdOrNull,
} from "@/components/util/grid/ymdFormat"; } from "@/components/util/grid/ymdFormat";
import { statisticsInputSewageReservoirPumpApi } from "@/entities/statistics/api/input/statistics-input-sewage-reservoir-pump-service"; import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
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 { import {
SEWAGE_RESERVOIR_STATS_CD, patchFooterVerificationByTab,
type SewageReservoirItem, patchVerificationFailedByTab,
type SewageReservoirRequestParams, } from "@/components/util/input/verification-failed-flag";
type SewageReservoirSaveItem, import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
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";
import { useAdminStore } from "@/store/admin-store"; import { useAdminStore } from "@/store/admin-store";
import { useTabStore } from "@/store/tab-store";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
const SewageReservoirPumpInpTabList = [ const SewageReservoirPumpInpTabList = [
SewageReservoirInpColumn, SewageReservoirInpColumn,
@@ -92,12 +107,6 @@ const firstTabKey = SewageReservoirPumpInpTabList[0]?.tabKey ?? SEWAGE_RESERVOIR
const rainfallPumpTabKey = const rainfallPumpTabKey =
RainfallPumpStationInpColumn.tabKey ?? "rainfallPumpStationInp"; 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 = const SEWAGE_RESERVOIR_FACILITY_NAME_GUIDE_MESSAGE =
"하수저류시설 등록시 하수저류시설명이 동일할 경우 같은 데이터로 분류됩니다.\n\n" + "하수저류시설 등록시 하수저류시설명이 동일할 경우 같은 데이터로 분류됩니다.\n\n" +
"하수저류시설명이 동일 할 경우 구분을 위해 하수저류시설 1, 하수저류시설 2 형태로 구분하여 주시길 바랍니다."; "하수저류시설명이 동일 할 경우 구분을 위해 하수저류시설 1, 하수저류시설 2 형태로 구분하여 주시길 바랍니다.";
@@ -116,8 +125,8 @@ const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -125,10 +134,22 @@ const computeTabEnterable = (
return periodEnterable; return periodEnterable;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || "") return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== "ALL") }
: true;
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( function computeTabToolbarSnapshot(
@@ -141,8 +162,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = const showSubmittedInInputPeriodToolbar =
isSubmitted && periodEnterable && gugunOk; isSubmitted && periodEnterable && gugunOk;
@@ -170,9 +191,8 @@ function buildSewageReservoirSearchParam(
const fcltNm = values.fctName?.trim(); const fcltNm = values.fctName?.trim();
return { return {
statsYr: values.year, statsYr: values.year,
sewerBzmnCd: values.bsnmCd,
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
...(fcltNm ? { fcltNm } : {}), ...(fcltNm ? { fcltNm } : {}),
...(overrides?.mode ? { mode: overrides.mode } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}),
}; };
@@ -245,15 +265,16 @@ function buildSggNmColumn(
function mapSewageReservoirListToGridRows( function mapSewageReservoirListToGridRows(
list: SewageReservoirItem[], list: SewageReservoirItem[],
ctpvNm: string, ctpvNm: string,
values?: SearchFormValues | null,
): GridRowWithVerification[] { ): GridRowWithVerification[] {
return list.map((item) => { return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
const row = toGridRows([ const row = toGridRows([
{ {
...item, ...item,
ctpvNm, ctpvNm,
oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd), oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd),
fldngPrvtnYn: normalizeOxYnValue(item.fldngPrvtnYn), fldngPrvtnYn: normalizeOxYnValue(item.fldngPrvtnYn),
wqPlltnMtgtYn: normalizeOxYnValue(item.wqPlltnMtgtYn), wtpltMtgtYn: normalizeOxYnValue(item.wtpltMtgtYn),
rtlztnUsgYn: normalizeOxYnValue(item.rtlztnUsgYn), rtlztnUsgYn: normalizeOxYnValue(item.rtlztnUsgYn),
}, },
])[0] as GridRowWithVerification; ])[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 { function toGridNumber(value: string | number | null | undefined): number {
const n = Number(value); const n = Number(value);
return Number.isFinite(n) ? n : 0; return Number.isFinite(n) ? n : 0;
@@ -290,13 +304,13 @@ function toPumpDmtrExpln(value: string | number | null | undefined): number {
function resolveSewageReservoirAdmdst( function resolveSewageReservoirAdmdst(
sggNm: string, sggNm: string,
sggOptions: Sgg[], sggOptions: Sgg[],
): { admdstCd: string; admdstNm: string } { ): { admdstCd: string | null; admdstNm: string | null } {
const trimmed = sggNm.trim(); const trimmed = sggNm.trim();
const matched = sggOptions.find((s) => s.gugun === trimmed); const matched = sggOptions.find((s) => s.gugun === trimmed);
if (matched) { if (matched) {
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun }; return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
} }
return { admdstCd: "", admdstNm: trimmed }; return { admdstCd: null, admdstNm: nullableTrimmedString(trimmed) };
} }
/** 그리드 행 → ST00040001 저장/삭제 API 항목 */ /** 그리드 행 → ST00040001 저장/삭제 API 항목 */
@@ -308,48 +322,47 @@ function buildSewageReservoirSaveItem(
String(row.sggNm ?? ""), String(row.sggNm ?? ""),
sggOptions, sggOptions,
); );
const fcltSn = String(row.fcltSn ?? "").trim();
return { return {
admdstCd, admdstCd,
admdstNm, admdstNm,
// 항목 추가 직후 저장(신규) — 그리드 fcltSn 없음 → API에는 "1" // 항목 추가 직후 저장(신규) — 그리드 fcltSn 없음 → API에는 "1"
fcltSn: fcltSn, fcltSn: nullableTrimmedString(row.fcltSn),
fcltNm: String(row.fcltNm ?? ""), fcltNm: nullableTrimmedString(row.fcltNm),
fcltPstnCn: String(row.fcltPstnCn ?? ""), fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd), oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd),
planDesignFreqCnt: toGridNumber(row.planDesignFreqCnt), planDesignFreqCnt: toGridNumber(row.planDesignFreqCnt),
fldngPrvtnYn: normalizeOxYnValue(row.fldngPrvtnYn), fldngPrvtnYn: nullableTrimmedString(normalizeOxYnValue(row.fldngPrvtnYn)),
wqPlltnMtgtYn: normalizeOxYnValue(row.wqPlltnMtgtYn), wtpltMtgtYn: nullableTrimmedString(normalizeOxYnValue(row.wtpltMtgtYn)),
rtlztnUsgYn: normalizeOxYnValue(row.rtlztnUsgYn), rtlztnUsgYn: nullableTrimmedString(normalizeOxYnValue(row.rtlztnUsgYn)),
fcltStrctCn: nullableTrimmedString(row.fcltStrctCn), fcltStrctCn: nullableTrimmedString(row.fcltStrctCn),
wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea), wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea),
impdFcltCpct: toGridNumber(row.impdFcltCpct), impdFcltCpct: toGridNumber(row.impdFcltCpct),
grssPpqty: toGridNumber(row.grssPpqty), wholPpqty: toGridNumber(row.wholPpqty),
pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln), pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln),
pumpFcltN1PerPpqty: toGridNumber(row.pumpFcltN1PerPpqty), pumpFcltN1Ppqty: toGridNumber(row.pumpFcltN1Ppqty),
pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt), pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt),
pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt), pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt),
pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln), pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln),
pumpFcltN2PerPpqty: toGridNumber(row.pumpFcltN2PerPpqty), pumpFcltN2Ppqty: toGridNumber(row.pumpFcltN2Ppqty),
pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt), pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt),
pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt), pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt),
pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln), pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln),
pumpFcltN3PerPpqty: toGridNumber(row.pumpFcltN3PerPpqty), pumpFcltN3Ppqty: toGridNumber(row.pumpFcltN3Ppqty),
pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt), pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt),
pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt), pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt),
prcsgFcltYn: String(row.prcsgFcltYn ? (row.prcsgFcltYn === '무' ? 'N' : 'Y') : ""), prcsgFcltYn: row.prcsgFcltYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
ddrztnEqpmYn: String(row.ddrztnEqpmYn ? (row.prcsgFcltYn === '무' ? 'N' : 'Y') : ""), odorEqpmYn: row.odorEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
oslfEtgtYn: String(row.oslfEtgtYn ? (row.prcsgFcltYn === '무' ? 'N' : 'Y') : ""), oslfEtgtYn: row.oslfEtgtYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
mltvMthCn: nullableTrimmedString(row.mltvMthCn), drgptnCn: nullableTrimmedString(row.drgptnCn),
impdMthCn: nullableTrimmedString(row.impdMthCn), impdMthCn: nullableTrimmedString(row.impdMthCn),
impdPrcsMthdCn: nullableTrimmedString(row.impdPrcsMthdCn), impdPrcsCn: nullableTrimmedString(row.impdPrcsCn),
dschgWtrsExpln: String(row.dschgWtrsExpln ?? ""), dschgWtrsExpln: nullableTrimmedString(row.dschgWtrsExpln),
prcptnLastDspsQnt: toGridNumber(row.prcptnLastDspsQnt), prpwLastDspsQnt: toGridNumber(row.prpwLastDspsQnt),
ctmnLastDspsQnt: toGridNumber(row.ctmnLastDspsQnt), ctmnLastDspsQnt: toGridNumber(row.ctmnLastDspsQnt),
prcptnLastDspsMthdExpln: String(row.prcptnLastDspsMthdExpln ?? ""), prpwLastDspsExpln: nullableTrimmedString(row.prpwLastDspsExpln),
ctmnLastDspsMthdExpln: String(row.ctmnLastDspsMthdExpln ?? ""), ctmnLastDspsExpln: nullableTrimmedString(row.ctmnLastDspsExpln),
oprtngDayCnt: toGridNumber(row.oprtngDayCnt), oprtngDayCnt: toGridNumber(row.oprtngDayCnt),
grssMltvQnt: toGridNumber(row.grssMltvQnt), wholQnt: toGridNumber(row.wholQnt),
rmrk: nullableTrimmedString(row.rmrk), rmrk: nullableTrimmedString(row.rmrk),
}; };
} }
@@ -363,43 +376,42 @@ function buildRainfallPumpStationSaveItem(
String(row.sggNm ?? ""), String(row.sggNm ?? ""),
sggOptions, sggOptions,
); );
const fcltSn = String(row.fcltSn ?? "").trim();
return { return {
admdstCd, admdstCd,
admdstNm, admdstNm,
fcltSn: fcltSn, fcltSn: nullableTrimmedString(row.fcltSn),
fcltNm: String(row.fcltNm ?? ""), fcltNm: nullableTrimmedString(row.fcltNm),
fcltPstnCn: String(row.fcltPstnCn ?? ""), fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
fcltyKndCn: String(row.fcltyKndCn ?? ""), fcltyKndCn: nullableTrimmedString(row.fcltyKndCn),
oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd), oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd),
planDesignFreqCnt: toGridNumber(row.planDesignFreqCnt), planDesignFreqCnt: toGridNumber(row.planDesignFreqCnt),
wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea), wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea),
rsvrYn: String(row.rsvrYn ? (row.rsvrYn === "무" ? "N" : "Y") : ""), runwtrYn: row.runwtrYn ? (row.runwtrYn === "무" ? "N" : "Y") : null,
rsvrImpndQnt: toGridNumber(row.rsvrImpndQnt), runwtrStrqty: toGridNumber(row.runwtrStrqty),
abstnRgnImpdQnt: toGridNumber(row.abstnRgnImpdQnt), abstnRgnImpdQnt: toGridNumber(row.abstnRgnImpdQnt),
grssPpqty: toGridNumber(row.grssPpqty), wholPpqty: toGridNumber(row.wholPpqty),
pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln), pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln),
pumpFcltN1PerPpqty: toGridNumber(row.pumpFcltN1PerPpqty), pumpFcltN1UnitPpqty: toGridNumber(row.pumpFcltN1UnitPpqty),
pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt), pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt),
pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt), pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt),
pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln), pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln),
pumpFcltN2PerPpqty: toGridNumber(row.pumpFcltN2PerPpqty), pumpFcltN2UnitPpqty: toGridNumber(row.pumpFcltN2UnitPpqty),
pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt), pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt),
pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt), pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt),
pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln), pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln),
pumpFcltN3PerPpqty: toGridNumber(row.pumpFcltN3PerPpqty), pumpFcltN3UnitPpqty: toGridNumber(row.pumpFcltN3UnitPpqty),
pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt), pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt),
pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt), pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt),
prcsgFcltYn: String(row.prcsgFcltYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : ""), prcsgFcltYn: row.prcsgFcltYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
ddrztnEqpmYn: String(row.ddrztnEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : ""), odorEqpmYn: row.odorEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
oslfEtgtYn: String(row.oslfEtgtYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : ""), oslfEtgtYn: row.oslfEtgtYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
dschgWtrsExpln: String(row.dschgWtrsExpln ?? ""), dschgWtrsExpln: nullableTrimmedString(row.dschgWtrsExpln),
prcptnLastDspsQnt: toGridNumber(row.prcptnLastDspsQnt), prpwLastDspsQnt: toGridNumber(row.prpwLastDspsQnt),
ctmnLastDspsQnt: toGridNumber(row.ctmnLastDspsQnt), ctmnLastDspsQnt: toGridNumber(row.ctmnLastDspsQnt),
prcptnLastDspsMthdExpln: String(row.prcptnLastDspsMthdExpln ?? ""), prpwLastDspsExpln: nullableTrimmedString(row.prpwLastDspsExpln),
ctmnLastDspsMthdExpln: String(row.ctmnLastDspsMthdExpln ?? ""), ctmnLastDspsExpln: nullableTrimmedString(row.ctmnLastDspsExpln),
oprtngDayCnt: toGridNumber(row.oprtngDayCnt), oprtngDayCnt: toGridNumber(row.oprtngDayCnt),
grssMltvQnt: toGridNumber(row.grssMltvQnt), wholQnt: toGridNumber(row.wholQnt),
rmrk: nullableTrimmedString(row.rmrk), rmrk: nullableTrimmedString(row.rmrk),
}; };
} }
@@ -407,16 +419,17 @@ function buildRainfallPumpStationSaveItem(
function mapRainfallPumpStationListToGridRows( function mapRainfallPumpStationListToGridRows(
list: RainfallPumpStationItem[], list: RainfallPumpStationItem[],
ctpvNm: string, ctpvNm: string,
values?: SearchFormValues | null,
): GridRowWithVerification[] { ): GridRowWithVerification[] {
return list.map((item) => { return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
const row = toGridRows([ const row = toGridRows([
{ {
...item, ...item,
ctpvNm, ctpvNm,
oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd), oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd),
rsvrYn: normalizeOxYnValue(item.rsvrYn), runwtrYn: normalizeOxYnValue(item.runwtrYn),
prcsgFcltYn: normalizeOxYnValue(item.prcsgFcltYn), prcsgFcltYn: normalizeOxYnValue(item.prcsgFcltYn),
ddrztnEqpmYn: normalizeOxYnValue(item.ddrztnEqpmYn), odorEqpmYn: normalizeOxYnValue(item.odorEqpmYn),
oslfEtgtYn: normalizeOxYnValue(item.oslfEtgtYn), oslfEtgtYn: normalizeOxYnValue(item.oslfEtgtYn),
}, },
])[0] as GridRowWithVerification; ])[0] as GridRowWithVerification;
@@ -446,15 +459,17 @@ async function waitForImrJobCompleted(
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
// 하수도 통계 관리 > 04. 하수저류 시설 및 빗물 펌프장 // 하수도 통계 관리 > 04. 하수저류 시설 및 빗물 펌프장
const SewageReservoirPumpInp = () => { const SewageReservoirPumpInp = () => {
const { sidoList } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
@@ -486,9 +501,9 @@ const SewageReservoirPumpInp = () => {
alert("시군구를 선택해주세요."); alert("시군구를 선택해주세요.");
return; 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); const currentData = getCurrentRowsForTab(tabKey);
if (!noApplicable && currentData.length === 0) { if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -513,14 +528,10 @@ const SewageReservoirPumpInp = () => {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -671,7 +682,7 @@ const SewageReservoirPumpInp = () => {
if (!(await confirm("저장 하시겠습니까?"))) return; if (!(await confirm("저장 하시겠습니까?"))) return;
commitGridForTab(tabKey); 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 currentData = getCurrentRowsForTab(tabKey);
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD; const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
const saveList = noApplicable const saveList = noApplicable
@@ -690,7 +701,7 @@ const SewageReservoirPumpInp = () => {
: rainfallPumpStationDeleteList : rainfallPumpStationDeleteList
.filter((row) => String(row.fcltSn ?? "").trim() !== "") .filter((row) => String(row.fcltSn ?? "").trim() !== "")
.map((row) => buildRainfallPumpStationSaveItem(row, sggOptions)); .map((row) => buildRainfallPumpStationSaveItem(row, sggOptions));
if (!noApplicable && saveList.length === 0) { if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -698,11 +709,7 @@ const SewageReservoirPumpInp = () => {
const requestBase = { const requestBase = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
...(noApplicable ? { noApplicable: true } : {}), ...(noApplicable ? { noApplicable: true } : {}),
}; };
@@ -713,13 +720,13 @@ const SewageReservoirPumpInp = () => {
const response = isReservoirTab const response = isReservoirTab
? await statisticsInputSewageReservoirPumpApi.saveSewageReservoir({ ? await statisticsInputSewageReservoirPumpApi.saveSewageReservoir({
...requestBase, ...requestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: saveList as SewageReservoirSaveItem[], saveList: saveList as SewageReservoirSaveItem[],
deleteList: deleteList as SewageReservoirSaveItem[], deleteList: deleteList as SewageReservoirSaveItem[],
}) })
: await statisticsInputSewageReservoirPumpApi.saveRainfallPumpStation({ : await statisticsInputSewageReservoirPumpApi.saveRainfallPumpStation({
...requestBase, ...requestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: saveList as RainfallPumpStationSaveItem[], saveList: saveList as RainfallPumpStationSaveItem[],
deleteList: deleteList as RainfallPumpStationSaveItem[], deleteList: deleteList as RainfallPumpStationSaveItem[],
}); });
@@ -762,7 +769,7 @@ const SewageReservoirPumpInp = () => {
} }
commitGridForTab(tabKey); 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 currentData = getCurrentRowsForTab(tabKey);
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD; const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
const submitList = noApplicable const submitList = noApplicable
@@ -781,12 +788,8 @@ const SewageReservoirPumpInp = () => {
const activeTabStatus = tabStatusInfoMap.get(tabKey) ?? null; const activeTabStatus = tabStatusInfoMap.get(tabKey) ?? null;
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey; const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey;
const verificationQuery = { const verificationQuery = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -800,7 +803,7 @@ const SewageReservoirPumpInp = () => {
await loadActiveTabData(tabKey, lastSearchedValues, true, false); await loadActiveTabData(tabKey, lastSearchedValues, true, false);
}; };
if (!noApplicable && submitList.length === 0) { if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -808,20 +811,16 @@ const SewageReservoirPumpInp = () => {
const submitRequestBase = { const submitRequestBase = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
...(noApplicable ? { noApplicable: true } : {}), ...(noApplicable ? { noApplicable: true } : {}),
laePicNm: submitInfo.laePicNm, lclgvPicNm: submitInfo.lclgvPicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
laeIdfrNm: submitInfo.laeIdfrNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
}; };
try { try {
@@ -831,13 +830,13 @@ const SewageReservoirPumpInp = () => {
const response = isReservoirTab const response = isReservoirTab
? await statisticsInputSewageReservoirPumpApi.submitSewageReservoir({ ? await statisticsInputSewageReservoirPumpApi.submitSewageReservoir({
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: submitList as SewageReservoirSaveItem[], submitList: submitList as SewageReservoirSaveItem[],
deleteList: deleteList as SewageReservoirSaveItem[], deleteList: deleteList as SewageReservoirSaveItem[],
}) })
: await statisticsInputSewageReservoirPumpApi.submitRainfallPumpStation({ : await statisticsInputSewageReservoirPumpApi.submitRainfallPumpStation({
...submitRequestBase, ...submitRequestBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: submitList as RainfallPumpStationSaveItem[], submitList: submitList as RainfallPumpStationSaveItem[],
deleteList: deleteList as RainfallPumpStationSaveItem[], deleteList: deleteList as RainfallPumpStationSaveItem[],
}); });
@@ -852,15 +851,16 @@ const SewageReservoirPumpInp = () => {
if (finalStatus === "5") { if (finalStatus === "5") {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중..."); setGlobalProgressLabel("시계열 검증 진행 중...");
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
stats_cd: statsCdForTab, stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? "", stats_yr: lastSearchedValues.year ?? "",
lae_sbmsn_stts_cd: lae_sbmsn_stts_cd:
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2", activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
gen_verify_stts_cd: nvpassed ? "1" : "2", gen_verify_stts_cd: nvpassed ? "1" : "2",
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -934,13 +934,19 @@ const SewageReservoirPumpInp = () => {
const [tabStatusInfoMap, setTabStatusInfoMap] = useState< const [tabStatusInfoMap, setTabStatusInfoMap] = useState<
Map<string, SewerageStatusInfo | null> Map<string, SewerageStatusInfo | null>
>(new Map()); >(new Map());
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
Map<string, TabToolbarSnapshot> Map<string, TabToolbarSnapshot>
>(new Map()); >(new Map());
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
() => new Map(),
);
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map()); const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
Map<string, boolean> Map<string, boolean>
>(new Map()); >(new Map());
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
Map<string, VerificationResultLikeMap | null>
>(new Map());
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = const [verificationResultDialogOpen, setVerificationResultDialogOpen] =
useState(false); useState(false);
@@ -957,35 +963,29 @@ const SewageReservoirPumpInp = () => {
useRef<(() => Record<string, string | number>[]) | null>(null); useRef<(() => Record<string, string | number>[]) | null>(null);
const sggOptionsSidoRef = useRef<string | null>(null); const sggOptionsSidoRef = useRef<string | null>(null);
const activeTabId = useTabStore((s) => s.activeTabId);
useEffect(() => { 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; let cancelled = false;
void (async () => { void (async () => {
const entries = await Promise.all( const next = await fetchColumnGuidesRecord(statsCd);
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;
}
}),
);
if (cancelled) return; if (cancelled) return;
setColumnGuidesByTab(Object.fromEntries(entries)); setColumnGuidesByTab((prev) =>
Object.prototype.hasOwnProperty.call(prev, tabKey)
? prev
: { ...prev, [tabKey]: next },
);
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, [activeTabId, activeTab, columnGuidesByTab]);
const searchDoneRef = useRef(false); const searchDoneRef = useRef(false);
const periodEnterableRef = useRef(true); const periodEnterableRef = useRef(true);
@@ -1035,6 +1035,13 @@ const SewageReservoirPumpInp = () => {
); );
return next; return next;
}); });
if (searchContext) {
setSearchContextByTab((prev) => {
const next = new Map(prev);
next.set(tabKey, searchContext);
return next;
});
}
}; };
const applyRowMap = ( const applyRowMap = (
@@ -1079,6 +1086,7 @@ const SewageReservoirPumpInp = () => {
const gridRows = mapSewageReservoirListToGridRows( const gridRows = mapSewageReservoirListToGridRows(
list, list,
commonInfo.ctpvNm, commonInfo.ctpvNm,
values,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -1089,6 +1097,9 @@ const SewageReservoirPumpInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) { if (updateLastSearchedValues) {
setLastSearchedValues(values); setLastSearchedValues(values);
} }
@@ -1124,6 +1135,7 @@ const SewageReservoirPumpInp = () => {
const gridRows = mapRainfallPumpStationListToGridRows( const gridRows = mapRainfallPumpStationListToGridRows(
list, list,
commonInfo.ctpvNm, commonInfo.ctpvNm,
values,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -1134,6 +1146,9 @@ const SewageReservoirPumpInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) { if (updateLastSearchedValues) {
setLastSearchedValues(values); setLastSearchedValues(values);
} }
@@ -1201,7 +1216,9 @@ const SewageReservoirPumpInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setLastSearchedValues(null); setLastSearchedValues(null);
setSggOptions([]); setSggOptions([]);
sggOptionsSidoRef.current = null; sggOptionsSidoRef.current = null;
@@ -1251,7 +1268,9 @@ const SewageReservoirPumpInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
loadedTabKeysRef.current.clear(); loadedTabKeysRef.current.clear();
setActiveTab(firstTabKey); 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 activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const activeTabToolbar = const activeTabToolbar =
toolbarSnapshotByTab.get(activeTab) ??
computeTabToolbarSnapshot( computeTabToolbarSnapshot(
activeTabStatus, activeTabStatus,
tabRowDataExists(rowDataMap, activeTab), tabRowDataExists(rowDataMap, activeTab),
@@ -1345,6 +1363,7 @@ const SewageReservoirPumpInp = () => {
baseShowEnterableToolbarButtons && toolbarGroupAllowed; baseShowEnterableToolbarButtons && toolbarGroupAllowed;
const showActionToolbarCard = const showActionToolbarCard =
baseShowActionToolbarCard && toolbarGroupAllowed; baseShowActionToolbarCard && toolbarGroupAllowed;
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const isReservoirOrRainfallTab = const isReservoirOrRainfallTab =
activeTab === SEWAGE_RESERVOIR_STATS_CD || activeTab === rainfallPumpTabKey; activeTab === SEWAGE_RESERVOIR_STATS_CD || activeTab === rainfallPumpTabKey;
const showArticleToolbarCard = const showArticleToolbarCard =
@@ -1359,7 +1378,8 @@ const SewageReservoirPumpInp = () => {
activeTab, activeTab,
tabRowDataExists(rowDataMap, activeTab), tabRowDataExists(rowDataMap, activeTab),
tabStatusInfoMap.get(activeTab), tabStatusInfoMap.get(activeTab),
); ) &&
submitOk;
const setIsNotApplicableForActiveTab: Dispatch<SetStateAction<boolean>> = useCallback( const setIsNotApplicableForActiveTab: Dispatch<SetStateAction<boolean>> = useCallback(
(next) => { (next) => {
const tabKey = activeTabRef.current; const tabKey = activeTabRef.current;
@@ -1438,10 +1458,10 @@ const SewageReservoirPumpInp = () => {
for (const tab of SewageReservoirPumpInpTabList) { for (const tab of SewageReservoirPumpInpTabList) {
const tabKey = tab.tabKey ?? ""; const tabKey = tab.tabKey ?? "";
const canEdit = const canEdit =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD; const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
const isRainfallPumpTab = tabKey === rainfallPumpTabKey; const isRainfallPumpTab = tabKey === rainfallPumpTabKey;
const rawColumns = tab.columns ?? []; const rawColumns = tab.columns ?? [];
@@ -1500,12 +1520,12 @@ const SewageReservoirPumpInp = () => {
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" e.vrfcCd === "IMR"
? "IMR검증 - " ? "시계열 검증 - "
: e.vrfcCd === "NV" : e.vrfcCd === "NV"
? "일반검증 - " ? "일반검증 - "
: "HB검증 -"; : "모집단 검증 -";
return `${prefix}${e.rsnExpln} return `${prefix}${e.rsnExpln}
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; (검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
@@ -1517,9 +1537,8 @@ const SewageReservoirPumpInp = () => {
const statsCd = tabStatsCdMap.get(activeTab); const statsCd = tabStatsCdMap.get(activeTab);
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -1540,6 +1559,7 @@ const SewageReservoirPumpInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
fctName={true} fctName={true}
@@ -1561,10 +1581,10 @@ const SewageReservoirPumpInp = () => {
<LastYearDataButton <LastYearDataButton
getLastYearDataFunc={() => void getLastYearDataFunc()} getLastYearDataFunc={() => void getLastYearDataFunc()}
/> />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{activeTabStatus?.laeSbmsnSttsCd === "2" && ( {activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
@@ -1606,11 +1626,11 @@ const SewageReservoirPumpInp = () => {
{SewageReservoirPumpInpTabList.map((tab) => { {SewageReservoirPumpInpTabList.map((tab) => {
const tabKey = tab.tabKey ?? ""; const tabKey = tab.tabKey ?? "";
const tabEnterableForGrid = const tabEnterableForGrid =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const gugunOk = buildGugunOkForToolbar(lastSearchedValues); const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD; const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
return ( return (
@@ -1668,6 +1688,9 @@ const SewageReservoirPumpInp = () => {
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd} excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
cellStyleCallback={verificationCellStyle} cellStyleCallback={verificationCellStyle}
footerVerificationResult={
footerVerificationByTab.get(tabKey) ?? null
}
cellTooltipCallback={verificationCellTooltip} cellTooltipCallback={verificationCellTooltip}
/> />
</TabsContent> </TabsContent>
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; 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 { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
@@ -35,7 +35,15 @@ import type {
cancelSubmitRequest, cancelSubmitRequest,
ImrStatusResponse, ImrStatusResponse,
SewerageStatusInfo, SewerageStatusInfo,
Sido,
} from "@/entities/statistics/model/statistics-interface"; } from "@/entities/statistics/model/statistics-interface";
import {
filterBySelectedAdmdstCd,
isGugunOkForToolbar,
isSubmitOkForToolbar,
toInputSearchRegion,
} from "@/entities/statistics";
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api"; 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 SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton"; import VerificationResultButton from "@/components/button/VerificationResultButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results"; import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type"; import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.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 { CauserBurdenInpColumn } from "@/components/util/column/input/sewerageFinance/CauserBurdenInpColumn";
import { SewerageChargeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageChargeInpColumn"; import { SewerageChargeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageChargeInpColumn";
import { SewerageFinanceSettleInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageFinanceSettleInpColumn"; import { SewerageFinanceSettleInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageFinanceSettleInpColumn";
import { SewerageUsageFeeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageUsageFeeInpColumn"; import { SewerageUsageFeeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageUsageFeeInpColumn";
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
import { toGridRows } from "@/components/util/grid/gridFunction"; 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 { useAdminStore } from "@/store/admin-store";
import { useTabStore } from "@/store/tab-store";
const SewerageFinanceInpTabList = [ const SewerageFinanceInpTabList = [
SewerageFinanceSettleInpColumn, SewerageFinanceSettleInpColumn,
@@ -73,10 +87,6 @@ const SewerageFinanceInpTabList = [
const firstTabKey = SewerageFinanceSettleInpColumn.tabKey; 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> & { type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: SewerageFinanceVerificationResult; verificationResult?: SewerageFinanceVerificationResult;
}; };
@@ -98,9 +108,8 @@ function buildSewerageFinanceSearchParam(
): SewerageFinanceRequestParams { ): SewerageFinanceRequestParams {
return { return {
statsYr: values.year, statsYr: values.year,
sewerBzmnCd: values.bsnmCd,
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
...(overrides?.mode ? { mode: overrides.mode } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}),
}; };
} }
@@ -109,8 +118,8 @@ const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -118,10 +127,14 @@ const computeTabEnterable = (
return periodEnterable; return periodEnterable;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || "") return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== "ALL") }
: true;
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
return isSubmitOkForToolbar(sidoListRef.current, values);
} }
function computeTabToolbarSnapshot( function computeTabToolbarSnapshot(
@@ -134,8 +147,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk; const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
const showActionToolbarCard = const showActionToolbarCard =
@@ -157,8 +170,8 @@ function tabRowDataExists(
function mapRegionalListToGridRows< function mapRegionalListToGridRows<
T extends { verificationResult?: SewerageFinanceVerificationResult }, T extends { verificationResult?: SewerageFinanceVerificationResult },
>(list: T[], ctpvNm: string): GridRowWithVerification[] { >(list: T[], ctpvNm: string, values?: SearchFormValues | null): GridRowWithVerification[] {
return list.map((item) => { return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
const { verificationResult, ...rest } = item; const { verificationResult, ...rest } = item;
const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification;
if (verificationResult) row.verificationResult = verificationResult; 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 }[]) { function buildColumnFormatNumber(columns: { fieldName?: string; dataFormat?: string }[]) {
const columnFormatMap = new Map<string, string>(); const columnFormatMap = new Map<string, string>();
columns.forEach((col) => { columns.forEach((col) => {
@@ -212,35 +214,35 @@ function buildSewerageFinanceSettleSaveList(
const formatNumber = buildColumnFormatNumber(SewerageFinanceSettleInpColumn.columns); const formatNumber = buildColumnFormatNumber(SewerageFinanceSettleInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm), sggNm: nullableTrimmedString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
cnterGovSprtAmt: formatNumber(d.cnterGovSprtAmt, "cnterGovSprtAmt"), soaGovSprtAmt: formatNumber(d.soaGovSprtAmt, "soaGovSprtAmt"),
mctpvLcexpAmt: formatNumber(d.mctpvLcexpAmt, "mctpvLcexpAmt"), mctpvLcexpAmt: formatNumber(d.mctpvLcexpAmt, "mctpvLcexpAmt"),
wareaSggLclAmt: formatNumber(d.wareaSggLclAmt, "wareaSggLclAmt"), wareaSggLclAmt: formatNumber(d.wareaSggLclAmt, "wareaSggLclAmt"),
sewerUseAmt: formatNumber(d.sewerUseAmt, "sewerUseAmt"), sewerUseAmt: formatNumber(d.sewerUseAmt, "sewerUseAmt"),
orgprBrdnAmt: formatNumber(d.orgprBrdnAmt, "orgprBrdnAmt"), fnnOrgprBrdnAmt: formatNumber(d.fnnOrgprBrdnAmt, "fnnOrgprBrdnAmt"),
etcIncmAmt: formatNumber(d.etcIncmAmt, "etcIncmAmt"), etcIncmAmt: formatNumber(d.etcIncmAmt, "etcIncmAmt"),
crfwAmt: formatNumber(d.crfwAmt, "crfwAmt"), soaCrfwAmt: formatNumber(d.soaCrfwAmt, "soaCrfwAmt"),
lbltAmt: formatNumber(d.lbltAmt, "lbltAmt"), soaLbltAmt: formatNumber(d.soaLbltAmt, "soaLbltAmt"),
tmplntFcltAmt: formatNumber(d.tmplntFcltAmt, "tmplntFcltAmt"), tmplntFcltAmt: formatNumber(d.tmplntFcltAmt, "tmplntFcltAmt"),
tmplntMendngAmt: formatNumber(d.tmplntMendngAmt, "tmplntMendngAmt"), tmplntAmt: formatNumber(d.tmplntAmt, "tmplntAmt"),
tmplntOperKeepAmt: formatNumber(d.tmplntOperKeepAmt, "tmplntOperKeepAmt"), tmplntOperKeepAmt: formatNumber(d.tmplntOperKeepAmt, "tmplntOperKeepAmt"),
tmplntEtcAmt: formatNumber(d.tmplntEtcAmt, "tmplntEtcAmt"), tmplntEtcAmt: formatNumber(d.tmplntEtcAmt, "tmplntEtcAmt"),
swagePplFcltAmt: formatNumber(d.swagePplFcltAmt, "swagePplFcltAmt"), swagePplFcltAmt: formatNumber(d.swagePplFcltAmt, "swagePplFcltAmt"),
swagePplMendngAmt: formatNumber(d.swagePplMendngAmt, "swagePplMendngAmt"), swagePplAmt: formatNumber(d.swagePplAmt, "swagePplAmt"),
swagePplOperKeepAmt: formatNumber(d.swagePplOperKeepAmt, "swagePplOperKeepAmt"), swagePplOperKeepAmt: formatNumber(d.swagePplOperKeepAmt, "swagePplOperKeepAmt"),
swagePplEtcAmt: formatNumber(d.swagePplEtcAmt, "swagePplEtcAmt"), swagePplEtcAmt: formatNumber(d.swagePplEtcAmt, "swagePplEtcAmt"),
swageImpdFcltAmt: formatNumber(d.swageImpdFcltAmt, "swageImpdFcltAmt"), swageImpdFcltAmt: formatNumber(d.swageImpdFcltAmt, "swageImpdFcltAmt"),
swageImpdMendngAmt: formatNumber(d.swageImpdMendngAmt, "swageImpdMendngAmt"), swageImpdAmt: formatNumber(d.swageImpdAmt, "swageImpdAmt"),
swageImpdOperKeepAmt: formatNumber(d.swageImpdOperKeepAmt, "swageImpdOperKeepAmt"), swageImpdOperKeepAmt: formatNumber(d.swageImpdOperKeepAmt, "swageImpdOperKeepAmt"),
swageImpdFcltEtcAmt: formatNumber(d.swageImpdFcltEtcAmt, "swageImpdFcltEtcAmt"), swageImpdFcltEtcAmt: formatNumber(d.swageImpdFcltEtcAmt, "swageImpdFcltEtcAmt"),
etcAcdntCrfwAmt: formatNumber(d.etcAcdntCrfwAmt, "etcAcdntCrfwAmt"), etcAcdntCrfwAmt: formatNumber(d.etcAcdntCrfwAmt, "etcAcdntCrfwAmt"),
etcNrcpAmt: formatNumber(d.etcNrcpAmt, "etcNrcpAmt"), etcNrcpAmt: formatNumber(d.etcNrcpAmt, "etcNrcpAmt"),
etcAmt: formatNumber(d.etcAmt, "etcAmt"), fnnEtcAmt: formatNumber(d.fnnEtcAmt, "fnnEtcAmt"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
fnnByWholPjtcoAmt: formatNumber(d.fnnByWholPjtcoAmt, "fnnByWholPjtcoAmt"), fnnSeWholPjtcoAmt: formatNumber(d.fnnSeWholPjtcoAmt, "fnnSeWholPjtcoAmt"),
usgByWholPjtcoAmt: formatNumber(d.usgByWholPjtcoAmt, "usgByWholPjtcoAmt"), usgSeWholPjtcoAmt: formatNumber(d.usgSeWholPjtcoAmt, "usgSeWholPjtcoAmt"),
tmplntWholAmt: formatNumber(d.tmplntWholAmt, "tmplntWholAmt"), tmplntWholAmt: formatNumber(d.tmplntWholAmt, "tmplntWholAmt"),
wholSwagePplAmt: formatNumber(d.wholSwagePplAmt, "wholSwagePplAmt"), wholSwagePplAmt: formatNumber(d.wholSwagePplAmt, "wholSwagePplAmt"),
wholSwageImpdFcltAmt: formatNumber(d.wholSwageImpdFcltAmt, "wholSwageImpdFcltAmt"), wholSwageImpdFcltAmt: formatNumber(d.wholSwageImpdFcltAmt, "wholSwageImpdFcltAmt"),
@@ -254,16 +256,16 @@ function buildSewerageChargeSaveList(
const formatNumber = buildColumnFormatNumber(SewerageChargeInpColumn.columns); const formatNumber = buildColumnFormatNumber(SewerageChargeInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm), sggNm: nullableTrimmedString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
anlLevyQnt: formatNumber(d.anlLevyQnt, "anlLevyQnt"), anlLevyQnt: formatNumber(d.anlLevyQnt, "anlLevyQnt"),
levyAmt: formatNumber(d.levyAmt, "levyAmt"), sewerLevyAmt: formatNumber(d.sewerLevyAmt, "sewerLevyAmt"),
grssPrmcoAmt: formatNumber(d.grssPrmcoAmt, "grssPrmcoAmt"), wholPrmcoAmt: formatNumber(d.wholPrmcoAmt, "wholPrmcoAmt"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
avgUntprcAmt: formatNumber(d.avgUntprcAmt, "avgUntprcAmt"), avgUntprcAmt: formatNumber(d.avgUntprcAmt, "avgUntprcAmt"),
grssUnitPrmcoAmt: formatNumber(d.grssUnitPrmcoAmt, "grssUnitPrmcoAmt"), wholUnitPrmcoAmt: formatNumber(d.wholUnitPrmcoAmt, "wholUnitPrmcoAmt"),
rlztnRt: formatNumber(d.rlztnRt, "rlztnRt"), sewerCrgRlztnRt: formatNumber(d.sewerCrgRlztnRt, "sewerCrgRlztnRt"),
})); }));
} }
@@ -273,33 +275,33 @@ function buildSewerageUsageFeeSaveList(
const formatNumber = buildColumnFormatNumber(SewerageUsageFeeInpColumn.columns); const formatNumber = buildColumnFormatNumber(SewerageUsageFeeInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm), sggNm: nullableTrimmedString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
hshusLevyAmt: formatNumber(d.hshusLevyAmt, "hshusLevyAmt"), hshusLevyAmt: formatNumber(d.hshusLevyAmt, "hshusLevyAmt"),
hshusLevyQnt: formatNumber(d.hshusLevyQnt, "hshusLevyQnt"), hshusLevyQnt: formatNumber(d.hshusLevyQnt, "hshusLevyQnt"),
hshusUntprcAmt: formatNumber(d.hshusUntprcAmt, "hshusUntprcAmt"), hshusUntprcAmt: formatNumber(d.hshusUntprcAmt, "hshusUntprcAmt"),
taskFrLevyAmt: formatNumber(d.taskFrLevyAmt, "taskFrLevyAmt"), ofuseLevyAmt: formatNumber(d.ofuseLevyAmt, "ofuseLevyAmt"),
taskFrLevyQnt: formatNumber(d.taskFrLevyQnt, "taskFrLevyQnt"), ofuseLevyQnt: formatNumber(d.ofuseLevyQnt, "ofuseLevyQnt"),
taskFrUntprcAmt: formatNumber(d.taskFrUntprcAmt, "taskFrUntprcAmt"), ofuseUntprcAmt: formatNumber(d.ofuseUntprcAmt, "ofuseUntprcAmt"),
fbuLevyAmt: formatNumber(d.fbuLevyAmt, "fbuLevyAmt"), fbuLevyAmt: formatNumber(d.fbuLevyAmt, "fbuLevyAmt"),
fbuLevyQnt: formatNumber(d.fbuLevyQnt, "fbuLevyQnt"), fbuLevyQnt: formatNumber(d.fbuLevyQnt, "fbuLevyQnt"),
fbuUntprcAmt: formatNumber(d.fbuUntprcAmt, "fbuUntprcAmt"), fbuUntprcAmt: formatNumber(d.fbuUntprcAmt, "fbuUntprcAmt"),
bthhsFrSpcs1LevyAmt: formatNumber(d.bthhsFrSpcs1LevyAmt, "bthhsFrSpcs1LevyAmt"), btht1LevyAmt: formatNumber(d.btht1LevyAmt, "btht1LevyAmt"),
bthhsFrSpcs1LevyQnt: formatNumber(d.bthhsFrSpcs1LevyQnt, "bthhsFrSpcs1LevyQnt"), btht1LevyQnt: formatNumber(d.btht1LevyQnt, "btht1LevyQnt"),
bthhsFrSpcs1UntprcAmt: formatNumber(d.bthhsFrSpcs1UntprcAmt, "bthhsFrSpcs1UntprcAmt"), btht1UntprcAmt: formatNumber(d.btht1UntprcAmt, "btht1UntprcAmt"),
bthhsFrSpcs2LevyAmt: formatNumber(d.bthhsFrSpcs2LevyAmt, "bthhsFrSpcs2LevyAmt"), btht2LevyAmt: formatNumber(d.btht2LevyAmt, "btht2LevyAmt"),
bthhsFrSpcs2LevyQnt: formatNumber(d.bthhsFrSpcs2LevyQnt, "bthhsFrSpcs2LevyQnt"), btht2LevyQnt: formatNumber(d.btht2LevyQnt, "btht2LevyQnt"),
bthhsFrSpcs2UntprcAmt: formatNumber(d.bthhsFrSpcs2UntprcAmt, "bthhsFrSpcs2UntprcAmt"), btht2UntprcAmt: formatNumber(d.btht2UntprcAmt, "btht2UntprcAmt"),
induLevyAmt: formatNumber(d.induLevyAmt, "induLevyAmt"), indstLevyAmt: formatNumber(d.indstLevyAmt, "indstLevyAmt"),
induLevyQnt: formatNumber(d.induLevyQnt, "induLevyQnt"), indstLevyQnt: formatNumber(d.indstLevyQnt, "indstLevyQnt"),
induUntprcAmt: formatNumber(d.induUntprcAmt, "induUntprcAmt"), indstUntprcAmt: formatNumber(d.indstUntprcAmt, "indstUntprcAmt"),
etcLevyAmt: formatNumber(d.etcLevyAmt, "etcLevyAmt"), etcLevyAmt: formatNumber(d.etcLevyAmt, "etcLevyAmt"),
etcLevyQnt: formatNumber(d.etcLevyQnt, "etcLevyQnt"), etcLevyQnt: formatNumber(d.etcLevyQnt, "etcLevyQnt"),
etcLevyUntprcAmt: formatNumber(d.etcLevyUntprcAmt, "etcLevyUntprcAmt"), etcLevyUntprcAmt: formatNumber(d.etcLevyUntprcAmt, "etcLevyUntprcAmt"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
levyAmt: formatNumber(d.levyAmt, "levyAmt"), sewerLevyAmt: formatNumber(d.sewerLevyAmt, "sewerLevyAmt"),
levyQnt: formatNumber(d.levyQnt, "levyQnt"), sewerLevyQnt: formatNumber(d.sewerLevyQnt, "sewerLevyQnt"),
})); }));
} }
@@ -309,17 +311,17 @@ function buildCauserBurdenSaveList(
const formatNumber = buildColumnFormatNumber(CauserBurdenInpColumn.columns); const formatNumber = buildColumnFormatNumber(CauserBurdenInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm), sggNm: nullableTrimmedString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
levyEvntcsCnt: formatNumber(d.levyEvntcsCnt, "levyEvntcsCnt"), brdnAmtLevyNocs: formatNumber(d.brdnAmtLevyNocs, "brdnAmtLevyNocs"),
levyAmt: formatNumber(d.levyAmt, "levyAmt"), brdnAmtLevyAmt: formatNumber(d.brdnAmtLevyAmt, "brdnAmtLevyAmt"),
mltvEqpmEvntcsCnt: formatNumber(d.mltvEqpmEvntcsCnt, "mltvEqpmEvntcsCnt"), drgNocs: formatNumber(d.drgNocs, "drgNocs"),
mltvEqpmAmt: formatNumber(d.mltvEqpmAmt, "mltvEqpmAmt"), drgAmt: formatNumber(d.drgAmt, "drgAmt"),
othrCstrnOthrActrCnt: formatNumber(d.othrCstrnOthrActrCnt, "othrCstrnOthrActrCnt"), othrCstrnOthrActrCnt: formatNumber(d.othrCstrnOthrActrCnt, "othrCstrnOthrActrCnt"),
othrCstrnOthrActrAmt: formatNumber(d.othrCstrnOthrActrAmt, "othrCstrnOthrActrAmt"), othrCstrnOthrActrAmt: formatNumber(d.othrCstrnOthrActrAmt, "othrCstrnOthrActrAmt"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
trgtByWholClctnAmt: formatNumber(d.trgtByWholClctnAmt, "trgtByWholClctnAmt"), 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))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
// 하수도 통계 관리 > 09. 하수도재정 // 하수도 통계 관리 > 09. 하수도재정
const SewerageFinanceInp = () => { const SewerageFinanceInp = () => {
const { sidoList } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
@@ -374,9 +378,15 @@ const SewerageFinanceInp = () => {
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
Map<string, boolean> Map<string, boolean>
>(new Map()); >(new Map());
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< const [footerVerificationByTab, setFooterVerificationByTab] = useState<
Map<string, VerificationResultLikeMap | null>
>(new Map());
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
Map<string, TabToolbarSnapshot> Map<string, TabToolbarSnapshot>
>(new Map()); >(new Map());
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
() => new Map(),
);
const [columnGuidesByTab, setColumnGuidesByTab] = useState< const [columnGuidesByTab, setColumnGuidesByTab] = useState<
Record<string, Record<string, string>> Record<string, Record<string, string>>
>({}); >({});
@@ -391,35 +401,29 @@ const SewerageFinanceInp = () => {
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null); const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
activeTabRef.current = activeTab; activeTabRef.current = activeTab;
const activeTabId = useTabStore((s) => s.activeTabId);
useEffect(() => { 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; let cancelled = false;
void (async () => { void (async () => {
const entries = await Promise.all( const next = await fetchColumnGuidesRecord(statsCd);
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;
}
}),
);
if (cancelled) return; if (cancelled) return;
setColumnGuidesByTab(Object.fromEntries(entries)); setColumnGuidesByTab((prev) =>
Object.prototype.hasOwnProperty.call(prev, tabKey)
? prev
: { ...prev, [tabKey]: next },
);
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, [activeTabId, activeTab, columnGuidesByTab]);
const commitToolbarSnapshotForLoadedTab = ( const commitToolbarSnapshotForLoadedTab = (
tabKey: string, tabKey: string,
@@ -442,6 +446,13 @@ const SewerageFinanceInp = () => {
); );
return next; return next;
}); });
if (searchContext) {
setSearchContextByTab((prev) => {
const next = new Map(prev);
next.set(tabKey, searchContext);
return next;
});
}
}; };
const applyRowMap = ( const applyRowMap = (
@@ -482,6 +493,7 @@ const SewerageFinanceInp = () => {
const gridRows = mapRegionalListToGridRows( const gridRows = mapRegionalListToGridRows(
(sewerageFinanceSettleList ?? []) as SewerageFinanceSettleItem[], (sewerageFinanceSettleList ?? []) as SewerageFinanceSettleItem[],
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -492,6 +504,9 @@ const SewerageFinanceInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
return true; return true;
@@ -508,6 +523,7 @@ const SewerageFinanceInp = () => {
const gridRows = mapRegionalListToGridRows( const gridRows = mapRegionalListToGridRows(
(sewerageChargeList ?? []) as SewerageChargeItem[], (sewerageChargeList ?? []) as SewerageChargeItem[],
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -518,6 +534,9 @@ const SewerageFinanceInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
return true; return true;
@@ -534,6 +553,7 @@ const SewerageFinanceInp = () => {
const gridRows = mapRegionalListToGridRows( const gridRows = mapRegionalListToGridRows(
(sewerageUsageFeeList ?? []) as SewerageUsageFeeItem[], (sewerageUsageFeeList ?? []) as SewerageUsageFeeItem[],
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -544,6 +564,9 @@ const SewerageFinanceInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
return true; return true;
@@ -560,6 +583,7 @@ const SewerageFinanceInp = () => {
const gridRows = mapRegionalListToGridRows( const gridRows = mapRegionalListToGridRows(
(causerBurdenList ?? []) as CauserBurdenItem[], (causerBurdenList ?? []) as CauserBurdenItem[],
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -570,6 +594,9 @@ const SewerageFinanceInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
return true; return true;
@@ -587,7 +614,9 @@ const SewerageFinanceInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setLastSearchedValues(null); setLastSearchedValues(null);
@@ -629,7 +658,9 @@ const SewerageFinanceInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
} }
@@ -739,14 +770,10 @@ const SewerageFinanceInp = () => {
} }
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -827,28 +854,20 @@ const SewerageFinanceInp = () => {
const submitInfoBase = { const submitInfoBase = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL" lclgvPicNm: submitInfo.lclgvPicNm,
? "" lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
: (lastSearchedValues.gugun ?? ""), lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laePicNm: submitInfo.laePicNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
}; };
const verificationQuery = { const verificationQuery = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -873,7 +892,7 @@ const SewerageFinanceInp = () => {
if (isSettleTab) { if (isSettleTab) {
const param: SewerageFinanceSettleSubmitRequest = { const param: SewerageFinanceSettleSubmitRequest = {
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: buildSewerageFinanceSettleSaveList(currentData), submitList: buildSewerageFinanceSettleSaveList(currentData),
...adminOpt, ...adminOpt,
}; };
@@ -884,7 +903,7 @@ const SewerageFinanceInp = () => {
} else if (isChargeTab) { } else if (isChargeTab) {
const param: SewerageChargeSubmitRequest = { const param: SewerageChargeSubmitRequest = {
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: buildSewerageChargeSaveList(currentData), submitList: buildSewerageChargeSaveList(currentData),
...adminOpt, ...adminOpt,
}; };
@@ -892,7 +911,7 @@ const SewerageFinanceInp = () => {
} else if (isUsageFeeTab) { } else if (isUsageFeeTab) {
const param: SewerageUsageFeeSubmitRequest = { const param: SewerageUsageFeeSubmitRequest = {
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: buildSewerageUsageFeeSaveList(currentData), submitList: buildSewerageUsageFeeSaveList(currentData),
...adminOpt, ...adminOpt,
}; };
@@ -901,7 +920,7 @@ const SewerageFinanceInp = () => {
} else { } else {
const param: CauserBurdenSubmitRequest = { const param: CauserBurdenSubmitRequest = {
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: buildCauserBurdenSaveList(currentData), submitList: buildCauserBurdenSaveList(currentData),
...adminOpt, ...adminOpt,
}; };
@@ -918,14 +937,15 @@ const SewerageFinanceInp = () => {
if (finalStatus === "5") { if (finalStatus === "5") {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중..."); setGlobalProgressLabel("시계열 검증 진행 중...");
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
stats_cd: statsCdForTab, stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? "", 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", gen_verify_stts_cd: nvpassed ? "1" : "2",
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -1009,11 +1029,7 @@ const SewerageFinanceInp = () => {
const baseRequest = { const baseRequest = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
}; };
try { try {
@@ -1023,7 +1039,7 @@ const SewerageFinanceInp = () => {
if (isSettleTab) { if (isSettleTab) {
const param: SewerageFinanceSettleSaveRequest = { const param: SewerageFinanceSettleSaveRequest = {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: buildSewerageFinanceSettleSaveList(currentData), saveList: buildSewerageFinanceSettleSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
}; };
@@ -1047,7 +1063,7 @@ const SewerageFinanceInp = () => {
if (isChargeTab) { if (isChargeTab) {
const param: SewerageChargeSaveRequest = { const param: SewerageChargeSaveRequest = {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: buildSewerageChargeSaveList(currentData), saveList: buildSewerageChargeSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
}; };
@@ -1071,7 +1087,7 @@ const SewerageFinanceInp = () => {
if (isUsageFeeTab) { if (isUsageFeeTab) {
const param: SewerageUsageFeeSaveRequest = { const param: SewerageUsageFeeSaveRequest = {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: buildSewerageUsageFeeSaveList(currentData), saveList: buildSewerageUsageFeeSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
}; };
@@ -1094,7 +1110,7 @@ const SewerageFinanceInp = () => {
const param: CauserBurdenSaveRequest = { const param: CauserBurdenSaveRequest = {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
saveList: buildCauserBurdenSaveList(currentData), saveList: buildCauserBurdenSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
}; };
@@ -1134,9 +1150,8 @@ const SewerageFinanceInp = () => {
const statsCd = tabStatsCdMap.get(activeTab); const statsCd = tabStatsCdMap.get(activeTab);
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -1147,10 +1162,10 @@ const SewerageFinanceInp = () => {
for (const tab of SewerageFinanceInpTabList) { for (const tab of SewerageFinanceInpTabList) {
const tabKey = tab.tabKey; const tabKey = tab.tabKey;
const canEdit = const canEdit =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const rawColumns = (tab.columns ?? []) as ColumnConfig[]; const rawColumns = (tab.columns ?? []) as ColumnConfig[];
const base = canEdit const base = canEdit
? rawColumns ? rawColumns
@@ -1167,19 +1182,20 @@ const SewerageFinanceInp = () => {
return map; return map;
}, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]); }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]);
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const activeTabToolbar = const activeTabToolbar =
toolbarSnapshotByTab.get(activeTab) ??
computeTabToolbarSnapshot( computeTabToolbarSnapshot(
activeTabStatus, activeTabStatus,
tabRowDataExists(rowDataMap, activeTab), tabRowDataExists(rowDataMap, activeTab),
hasSearched, hasSearched,
lastSearchedValues, searchContextForToolbar,
inputPeriodEnterable, inputPeriodEnterable,
isAdminInfo, isAdminInfo,
); );
const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } = const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } =
activeTabToolbar; activeTabToolbar;
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const verificationCellStyle = ({ const verificationCellStyle = ({
fieldName, fieldName,
@@ -1212,11 +1228,11 @@ const SewerageFinanceInp = () => {
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" e.vrfcCd === "IMR"
? "IMR검증 - " ? "시계열 검증 - "
: e.vrfcCd === "NV" : e.vrfcCd === "NV"
? "일반검증 - " ? "일반검증 - "
: "HB검증 - "; : "모집단 검증 - ";
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
@@ -1238,6 +1254,7 @@ const SewerageFinanceInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
onSubmit={handleSearch} onSubmit={handleSearch}
@@ -1245,8 +1262,8 @@ const SewerageFinanceInp = () => {
onValuesChange={setSearchValues} onValuesChange={setSearchValues}
/> />
{showActionToolbarCard && ( {showActionToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]"> <div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<VerificationResultButton <VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog} VerificationResultButtonFunc={openVerificationHistoryDialog}
isVerificationFailed={ isVerificationFailed={
@@ -1258,10 +1275,10 @@ const SewerageFinanceInp = () => {
<LastYearDataButton <LastYearDataButton
getLastYearDataFunc={() => void lastYearDataFunc()} getLastYearDataFunc={() => void lastYearDataFunc()}
/> />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{activeTabStatus?.laeSbmsnSttsCd === "2" && ( {activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
@@ -1293,11 +1310,11 @@ const SewerageFinanceInp = () => {
const tabKey = info.tabKey; const tabKey = info.tabKey;
const rowData = rowDataMap.get(tabKey) ?? []; const rowData = rowDataMap.get(tabKey) ?? [];
const tabEnterableForGrid = const tabEnterableForGrid =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const gugunOk = buildGugunOkForToolbar(lastSearchedValues); const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
return ( return (
@@ -1335,6 +1352,9 @@ const SewerageFinanceInp = () => {
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
isExcelExport isExcelExport
cellStyleCallback={verificationCellStyle} cellStyleCallback={verificationCellStyle}
footerVerificationResult={
footerVerificationByTab.get(tabKey) ?? null
}
cellTooltipCallback={verificationCellTooltip} cellTooltipCallback={verificationCellTooltip}
getGridDataRef={ getGridDataRef={
activeTab === tabKey ? getGridDataRef : undefined 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 { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; import { useAlertStore, useGlobalProgressStore, useSearchConditionSidoList, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
import { import {
STATISTICS_INPUT_URL_PATH, STATISTICS_INPUT_URL_PATH,
verificationSearchToFormValues, verificationSearchToFormValues,
@@ -25,8 +25,15 @@ import type {
cancelSubmitRequest, cancelSubmitRequest,
ImrStatusResponse, ImrStatusResponse,
SewerageStatusInfo, SewerageStatusInfo,
Sido,
SubmitExceptionRequest, SubmitExceptionRequest,
} from "@/entities/statistics/model/statistics-interface"; } from "@/entities/statistics/model/statistics-interface";
import {
filterBySelectedAdmdstCd,
isGugunOkForToolbar,
isSubmitOkForToolbar,
toInputSearchRegion,
} from "@/entities/statistics";
import { LoadingWithAnimation } from "@common/components"; import { LoadingWithAnimation } from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api"; 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 VerificationResultButton from "@/components/button/VerificationResultButton";
// import { DataGrid } from "@rsone/components"; // import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid"; 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 { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; 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 { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
import { getFooterSummary } from "@/components/util/grid/footerSummary"; import { getFooterSummary } from "@/components/util/grid/footerSummary";
import { toGridRows } from "@/components/util/grid/gridFunction"; 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 { 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 { useAdminStore } from "@/store/admin-store";
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store"; import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
import { useTabStore } from "@/store/tab-store"; import { useTabStore } from "@/store/tab-store";
@@ -60,6 +68,28 @@ type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: SewerageSupplyRateVerificationResult; 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 조회용 통계코드 (하수도 보급률) */ /** 컬럼 가이드 API 조회용 통계코드 (하수도 보급률) */
const SEWERAGE_PENETRATION_COLUMN_GUIDE_STATS_CD = "ST00010001"; 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))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
const sidoListRef: { current: Sido[] } = { current: [] };
function getSewerageSupplyRateParams( function getSewerageSupplyRateParams(
values: SearchFormValues, values: SearchFormValues,
overrides?: { yr?: string; demoYr?: string; mode?: string } overrides?: { yr?: string; demoYr?: string; mode?: string }
): SewerageSupplyRateRequestParams { ): SewerageSupplyRateRequestParams {
return { return {
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === 'ALL' ? '' : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
sewerBzmnCd: values.bsnmCd,
statsYr: overrides?.yr ?? values.year, statsYr: overrides?.yr ?? values.year,
...(overrides?.mode && { mode: overrides.mode }), ...(overrides?.mode && { mode: overrides.mode }),
}; };
} }
function normalizeGridString(value: unknown): string | null {
if (typeof value !== "string") {
return null;
}
return value.trim() === "" ? null : value;
}
// 하수도 통계 관리 > 01. 하수도 보급률 // 하수도 통계 관리 > 01. 하수도 보급률
const SeweragePenetrationRateInp = () => { const SeweragePenetrationRateInp = () => {
const { sidoList, isReady } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]); const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
@@ -137,6 +162,8 @@ const SeweragePenetrationRateInp = () => {
/** `engColNm` → `columnGuide` (헤더 hover) */ /** `engColNm` → `columnGuide` (헤더 hover) */
const [columnGuidesByEngCol, setColumnGuidesByEngCol] = useState<Record<string, string>>({}); const [columnGuidesByEngCol, setColumnGuidesByEngCol] = useState<Record<string, string>>({});
const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null); 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 { confirm } = useConfirm();
const showGlobalProgress = useGlobalProgressStore((state) => state.show); const showGlobalProgress = useGlobalProgressStore((state) => state.show);
const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress); const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress);
@@ -156,15 +183,16 @@ const SeweragePenetrationRateInp = () => {
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE) { if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE) {
return; return;
} }
if (!isReady) return;
const pending = useStatisticsInputFocusStore const pending = useStatisticsInputFocusStore
.getState() .getState()
.takePendingForUrl(STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE); .takePendingForUrl(STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE);
if (!pending?.search) return; if (!pending?.search) return;
const values = verificationSearchToFormValues(pending.search); const values = verificationSearchToFormValues(pending.search, sidoList);
setVerificationSearchDefaults(values); setVerificationSearchDefaults(values);
postVerificationSearchRef.current = values; postVerificationSearchRef.current = values;
setSearchFormRemountKey((k) => k + 1); setSearchFormRemountKey((k) => k + 1);
}, [activeTabId, inputFocusNonce]); }, [activeTabId, inputFocusNonce, isReady, sidoList]);
useEffect(() => { useEffect(() => {
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE) { 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), (footerFromApi && footerFromApi[fieldName] != null ? footerFromApi[fieldName] : null) ?? getFooterSummary(grid, column),
}); });
// 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요. // 관리자는 제출/승인 상태에서도 편집 가능. 단, 입력기간 외에는 불가.
const columnInfo = useMemo(() => { const columnInfo = useMemo(() => {
const { columns, columnLayout } = SeweragePenetrationRateInpColumn; 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 withHeaderTooltips = base.map((col) => {
const guide = columnGuidesByEngCol[col.fieldName]?.trim(); const guide = columnGuidesByEngCol[col.fieldName]?.trim();
@@ -233,7 +264,7 @@ const SeweragePenetrationRateInp = () => {
return col; return col;
}), }),
}; };
}, [isEnterable, isAdminInfo, footerFromApi, columnGuidesByEngCol]); }, [isEnterable, isAdminInfo, periodCheckList, footerFromApi, columnGuidesByEngCol]);
const handleReset = () => { const handleReset = () => {
setRowData([]); setRowData([]);
@@ -242,19 +273,28 @@ const SeweragePenetrationRateInp = () => {
setLastSearchedYear(null); setLastSearchedYear(null);
setLastSearchedValues(null); setLastSearchedValues(null);
setFooterFromApi(null); setFooterFromApi(null);
setFooterVerificationResult(null);
setVerificationSearchDefaults(null); setVerificationSearchDefaults(null);
setIsVerificationFailed(false); setIsVerificationFailed(false);
setSearchFormRemountKey((k) => k + 1); setSearchFormRemountKey((k) => k + 1);
}; };
const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams, isEnterable: boolean) => { const fetchSewerageSupplyRate = async (
params: SewerageSupplyRateRequestParams,
isEnterable: boolean,
values?: SearchFormValues | null,
) => {
try { try {
setIsLoading(true); setIsLoading(true);
const response = await statisticsInputSewSupplyRateApi.getSewerageSupplyRateList(params); const response = await statisticsInputSewSupplyRateApi.getSewerageSupplyRateList(params);
if (response.status === 'success' && response.data) { if (response.status === 'success' && response.data) {
const commonInfo = response.data.commonInfo; const commonInfo = response.data.commonInfo;
const statusInfo = response.data.statusInfo; 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, ...d,
ctpvNm: commonInfo.ctpvNm, ctpvNm: commonInfo.ctpvNm,
spcCtpnRgnCn: d.spcCtpnRgnCn === '' || d.spcCtpnRgnCn === null ? '해당없음' : d.spcCtpnRgnCn, spcCtpnRgnCn: d.spcCtpnRgnCn === '' || d.spcCtpnRgnCn === null ? '해당없음' : d.spcCtpnRgnCn,
@@ -278,10 +318,15 @@ const SeweragePenetrationRateInp = () => {
} }
: null : null
); );
setFooterVerificationResult(
remapCommonInfoVerificationToGridFields(
commonInfo.verificationResult,
),
);
setStatsCd(commonInfo.statsCd); setStatsCd(commonInfo.statsCd);
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo)); setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
if (statusInfo && statusInfo.laeSbmsnSttsCd) { if (statusInfo && statusInfo.lclgvSbmsnSttsCd) {
if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) { if (['2', '3'].includes(statusInfo.lclgvSbmsnSttsCd)) {
setIsEnterable(false); setIsEnterable(false);
} else { } else {
setIsEnterable(isEnterable); setIsEnterable(isEnterable);
@@ -299,6 +344,7 @@ const SeweragePenetrationRateInp = () => {
setRowData([]); setRowData([]);
setStatusInfo(null); setStatusInfo(null);
setFooterFromApi(null); setFooterFromApi(null);
setFooterVerificationResult(null);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -318,7 +364,7 @@ const SeweragePenetrationRateInp = () => {
setPeriodCheckList(periods); setPeriodCheckList(periods);
const checkEnterable = isInputPeriodCurrent(periods); const checkEnterable = isInputPeriodCurrent(periods);
setLastSearchedYear(values.year || null); setLastSearchedYear(values.year || null);
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values), checkEnterable); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values), checkEnterable, values);
setLastSearchedValues(values); setLastSearchedValues(values);
} else { } else {
alert(response.message); alert(response.message);
@@ -346,11 +392,10 @@ const SeweragePenetrationRateInp = () => {
const params: SubmitExceptionRequest = { const params: SubmitExceptionRequest = {
...submitInfo, ...submitInfo,
sewerBzmnCd: searchValues?.bsnmCd ?? '', ...toInputSearchRegion(sidoListRef.current, searchValues),
ctpvNm: searchValues?.sido ?? '', ctpvNm: searchValues?.sido ?? '',
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''),
statsYr: searchValues?.year ?? '', statsYr: searchValues?.year ?? '',
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
statsCd: statsCd, statsCd: statsCd,
}; };
@@ -364,7 +409,7 @@ const SeweragePenetrationRateInp = () => {
const response = await statisticsApi.submitException(params); const response = await statisticsApi.submitException(params);
if (response.status === 'success') { if (response.status === 'success') {
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false, searchValues ?? {});
alert('제출되었습니다.'); alert('제출되었습니다.');
} else { } else {
alert(response.message || '데이터 제출 중 오류가 발생했습니다.'); alert(response.message || '데이터 제출 중 오류가 발생했습니다.');
@@ -388,7 +433,7 @@ const SeweragePenetrationRateInp = () => {
return; return;
} }
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' }), true); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' }), true, values);
}; };
const submitFunc = async (isException: boolean) => { const submitFunc = async (isException: boolean) => {
@@ -453,52 +498,51 @@ const SeweragePenetrationRateInp = () => {
}; };
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({ const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
sggNm: normalizeGridString(d.sggNm), sggNm: nullableTrimmedString(d.sggNm),
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
trbtrNm: normalizeGridString(d.trbtrNm), statsTrbtrNm: nullableTrimmedString(d.statsTrbtrNm),
spcCtpnRgnCn: spcCtpnRgnCn:
d.spcCtpnRgnCn === '해당없음' ? null : normalizeGridString(d.spcCtpnRgnCn), d.spcCtpnRgnCn === '해당없음' ? null : nullableTrimmedString(d.spcCtpnRgnCn),
ts1SwtrmtPpltnCnt: formatNumber(d.ts1SwtrmtPpltnCnt, 'ts1SwtrmtPpltnCnt'), physSwtrmtPpltnCnt: formatNumber(d.physSwtrmtPpltnCnt, 'physSwtrmtPpltnCnt'),
ts2SwtrmtPpltnCnt: formatNumber(d.ts2SwtrmtPpltnCnt, 'ts2SwtrmtPpltnCnt'), crtureSwtrmtPpltnCnt: formatNumber(d.crtureSwtrmtPpltnCnt, 'crtureSwtrmtPpltnCnt'),
ts3SwtrmtPpltnCnt: formatNumber(d.ts3SwtrmtPpltnCnt, 'ts3SwtrmtPpltnCnt'), altdSwtrmtPpltnCnt: formatNumber(d.altdSwtrmtPpltnCnt, 'altdSwtrmtPpltnCnt'),
ts1WwtPrcsPpltnCnt: formatNumber(d.ts1WwtPrcsPpltnCnt, 'ts1WwtPrcsPpltnCnt'), physWswtPpltnCnt: formatNumber(d.physWswtPpltnCnt, 'physWswtPpltnCnt'),
ts2WwtPrcsPpltnCnt: formatNumber(d.ts2WwtPrcsPpltnCnt, 'ts2WwtPrcsPpltnCnt'), crtureWswtPpltnCnt: formatNumber(d.crtureWswtPpltnCnt, 'crtureWswtPpltnCnt'),
ts3WwtPrcsPpltnCnt: formatNumber(d.ts3WwtPrcsPpltnCnt, 'ts3WwtPrcsPpltnCnt'), altdWswtPpltnCnt: formatNumber(d.altdWswtPpltnCnt, 'altdWswtPpltnCnt'),
yetCntnPpltnCnt: formatNumber(d.yetCntnPpltnCnt, 'yetCntnPpltnCnt'), urftPpltnCnt: formatNumber(d.urftPpltnCnt, 'urftPpltnCnt'),
staaArea: formatNumber(d.staaArea, 'staaArea'), swtrmtZoneIntnar: formatNumber(d.swtrmtZoneIntnar, 'swtrmtZoneIntnar'),
unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'), unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'),
drtwPrcsPpltnCnt: formatNumber(d.drtwPrcsPpltnCnt, 'drtwPrcsPpltnCnt'), swgdplPpltnCnt: formatNumber(d.swgdplPpltnCnt, 'swgdplPpltnCnt'),
spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'), spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'),
staaExcpArea: formatNumber(d.staaExcpArea, 'staaExcpArea'), swtrmtZoneExtnar: formatNumber(d.swtrmtZoneExtnar, 'swtrmtZoneExtnar'),
rmrk: normalizeGridString(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'), wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'),
wholArea: formatNumber(d.wholArea, 'wholArea'), whlar: formatNumber(d.whlar, 'whlar'),
wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'), wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'),
wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'), wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'),
wholWwtPrcsPpltnCnt: formatNumber(d.wholWwtPrcsPpltnCnt, 'wholWwtPrcsPpltnCnt'), wholWswtPpltnCnt: formatNumber(d.wholWswtPpltnCnt, 'wholWswtPpltnCnt'),
wholStaaExcpPpltnCnt: formatNumber(d.wholStaaExcpPpltnCnt, 'wholStaaExcpPpltnCnt'), wholStaaOtsdPpltnCnt: formatNumber(d.wholStaaOtsdPpltnCnt, 'wholStaaOtsdPpltnCnt'),
staaPsplyRt: formatNumber(d.staaPsplyRt, 'staaPsplyRt'), swagePplPsplyRt: formatNumber(d.swagePplPsplyRt, 'swagePplPsplyRt'),
altdPrcsPsplyRt: formatNumber(d.altdPrcsPsplyRt, 'altdPrcsPsplyRt'), awtrPsplyRt: formatNumber(d.awtrPsplyRt, 'awtrPsplyRt'),
sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'), sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'),
})); }));
const requestData: SewerageSupplyRateSaveRequest = { const requestData: SewerageSupplyRateSaveRequest = {
sewerBzmnCd: searchValues?.bsnmCd ?? '', ...toInputSearchRegion(sidoListRef.current, searchValues),
ctpvNm: searchValues?.sido ?? '', ctpvNm: searchValues?.sido ?? '',
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''),
statsYr: searchValues?.year ?? '', statsYr: searchValues?.year ?? '',
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
submitList: convertedData, submitList: convertedData,
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.sewerBzmnFrgnrWholPpltnCnt, sewerBzmnArea: footerFromApi?.sewerBzmnArea,
laePicNm: submitInfo.laePicNm, lclgvPicNm: submitInfo.lclgvPicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
laeIdfrNm: submitInfo.laeIdfrNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
}; };
setGlobalProgressLabel('일반 검증 진행 중...'); setGlobalProgressLabel('일반 검증 진행 중...');
@@ -507,14 +551,15 @@ const SeweragePenetrationRateInp = () => {
setGlobalProgress(50); setGlobalProgress(50);
if (response.data.finalStatus === '5') { if (response.data.finalStatus === '5') {
setGlobalProgressLabel('IMR 검증 진행 중...'); setGlobalProgressLabel('시계열 검증 진행 중...');
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: searchValues?.bsnmCd ?? '', sewer_bzmn_cd: searchValues?.bsnmCd ?? '',
stats_cd: statsCd, stats_cd: statsCd,
stats_yr: searchValues?.year ?? '', 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', gen_verify_stts_cd: response.data.nvpassed ? '1' : '2',
hb_pass_yn: response.data.isHbPassed,
}; };
try { try {
@@ -530,13 +575,12 @@ const SeweragePenetrationRateInp = () => {
); );
} }
setGlobalProgress(100); setGlobalProgress(100);
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {});
await openVerificationResultDialogIfNeeded( await openVerificationResultDialogIfNeeded(
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
? { ? {
sewerBzmnCd: searchValues.bsnmCd, ...toInputSearchRegion(sidoListRef.current, searchValues),
ctpvNm: searchValues.sido, ctpvNm: searchValues.sido,
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
statsYr: searchValues.year, statsYr: searchValues.year,
statsCd, statsCd,
} }
@@ -545,14 +589,13 @@ const SeweragePenetrationRateInp = () => {
{ notify: alert }, { notify: alert },
); );
} catch (e) { } catch (e) {
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {});
setIsLoading(false); setIsLoading(false);
await openVerificationResultDialogIfNeeded( await openVerificationResultDialogIfNeeded(
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
? { ? {
sewerBzmnCd: searchValues.bsnmCd, ...toInputSearchRegion(sidoListRef.current, searchValues),
ctpvNm: searchValues.sido, ctpvNm: searchValues.sido,
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
statsYr: searchValues.year, statsYr: searchValues.year,
statsCd, statsCd,
} }
@@ -569,13 +612,12 @@ const SeweragePenetrationRateInp = () => {
} }
} else { } else {
setGlobalProgress(100); setGlobalProgress(100);
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false, searchValues ?? {});
await openVerificationResultDialogIfNeeded( await openVerificationResultDialogIfNeeded(
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
? { ? {
sewerBzmnCd: searchValues.bsnmCd, ...toInputSearchRegion(sidoListRef.current, searchValues),
ctpvNm: searchValues.sido, ctpvNm: searchValues.sido,
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
statsYr: searchValues.year, statsYr: searchValues.year,
statsCd, statsCd,
} }
@@ -636,51 +678,50 @@ const SeweragePenetrationRateInp = () => {
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({ const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
// 행정구역 // 행정구역
sggNm: normalizeGridString(d.sggNm), // 시군구 sggNm: nullableTrimmedString(d.sggNm), // 시군구
admdstCd: normalizeGridString(d.admdstCd), // 행정코드 admdstCd: nullableTrimmedString(d.admdstCd), // 행정코드
admdstNm: normalizeGridString(d.admdstNm), // 행정구역명 admdstNm: nullableTrimmedString(d.admdstNm), // 행정구역명
trbtrNm: normalizeGridString(d.trbtrNm), // 지류 statsTrbtrNm: nullableTrimmedString(d.statsTrbtrNm), // 지류
spcCtpnRgnCn: spcCtpnRgnCn:
d.spcCtpnRgnCn === '해당없음' d.spcCtpnRgnCn === '해당없음'
? null ? null
: normalizeGridString(d.spcCtpnRgnCn), // 특별대책지역 : nullableTrimmedString(d.spcCtpnRgnCn), // 특별대책지역
// 공공하수처리인구(명) - 물리적/생물학적/고도처리 // 공공하수처리인구(명) - 물리적/생물학적/고도처리
ts1SwtrmtPpltnCnt: formatNumber(d.ts1SwtrmtPpltnCnt, 'ts1SwtrmtPpltnCnt'), // 물리적처리 physSwtrmtPpltnCnt: formatNumber(d.physSwtrmtPpltnCnt, 'physSwtrmtPpltnCnt'), // 물리적처리
ts2SwtrmtPpltnCnt: formatNumber(d.ts2SwtrmtPpltnCnt, 'ts2SwtrmtPpltnCnt'), // 생물학적처리 crtureSwtrmtPpltnCnt: formatNumber(d.crtureSwtrmtPpltnCnt, 'crtureSwtrmtPpltnCnt'), // 생물학적처리
ts3SwtrmtPpltnCnt: formatNumber(d.ts3SwtrmtPpltnCnt, 'ts3SwtrmtPpltnCnt'), // 고도처리 altdSwtrmtPpltnCnt: formatNumber(d.altdSwtrmtPpltnCnt, 'altdSwtrmtPpltnCnt'), // 고도처리
// 폐수처리인구(명) - 물리적/생물학적/고도처리 // 폐수처리인구(명) - 물리적/생물학적/고도처리
ts1WwtPrcsPpltnCnt: formatNumber(d.ts1WwtPrcsPpltnCnt, 'ts1WwtPrcsPpltnCnt'), physWswtPpltnCnt: formatNumber(d.physWswtPpltnCnt, 'physWswtPpltnCnt'),
ts2WwtPrcsPpltnCnt: formatNumber(d.ts2WwtPrcsPpltnCnt, 'ts2WwtPrcsPpltnCnt'), crtureWswtPpltnCnt: formatNumber(d.crtureWswtPpltnCnt, 'crtureWswtPpltnCnt'),
ts3WwtPrcsPpltnCnt: formatNumber(d.ts3WwtPrcsPpltnCnt, 'ts3WwtPrcsPpltnCnt'), altdWswtPpltnCnt: formatNumber(d.altdWswtPpltnCnt, 'altdWswtPpltnCnt'),
yetCntnPpltnCnt: formatNumber(d.yetCntnPpltnCnt, 'yetCntnPpltnCnt'), // 미접속인구 urftPpltnCnt: formatNumber(d.urftPpltnCnt, 'urftPpltnCnt'), // 미접속인구
staaArea: formatNumber(d.staaArea, 'staaArea'), // 하수처리구역 내 면적(㎢) swtrmtZoneIntnar: formatNumber(d.swtrmtZoneIntnar, 'swtrmtZoneIntnar'), // 하수처리구역 내 면적(㎢)
// 하수처리구역 외 // 하수처리구역 외
unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'), // 미처리인구 unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'), // 미처리인구
drtwPrcsPpltnCnt: formatNumber(d.drtwPrcsPpltnCnt, 'drtwPrcsPpltnCnt'), // 오수처리인구 swgdplPpltnCnt: formatNumber(d.swgdplPpltnCnt, 'swgdplPpltnCnt'), // 오수처리인구
spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'), // 정화조인구 spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'), // 정화조인구
staaExcpArea: formatNumber(d.staaExcpArea, 'staaExcpArea'), // 하수처리구역 외 면적(㎢) swtrmtZoneExtnar: formatNumber(d.swtrmtZoneExtnar, 'swtrmtZoneExtnar'), // 하수처리구역 외 면적(㎢)
rmrk: normalizeGridString(d.rmrk), // 비고 rmrk: nullableTrimmedString(d.rmrk), // 비고
// 합계·소계(계산필드 저장용) // 합계·소계(계산필드 저장용)
wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'), // 총인구(명) wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'), // 총인구(명)
wholArea: formatNumber(d.wholArea, 'wholArea'), // 총면적(㎢) whlar: formatNumber(d.whlar, 'whlar'), // 총면적(㎢)
wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'), // 하수처리구역 내 합계 wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'), // 하수처리구역 내 합계
wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'), // 공공하수처리인구 소계 wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'), // 공공하수처리인구 소계
wholWwtPrcsPpltnCnt: formatNumber(d.wholWwtPrcsPpltnCnt, 'wholWwtPrcsPpltnCnt'), // 폐수처리인구 소계 wholWswtPpltnCnt: formatNumber(d.wholWswtPpltnCnt, 'wholWswtPpltnCnt'), // 폐수처리인구 소계
wholStaaExcpPpltnCnt: formatNumber(d.wholStaaExcpPpltnCnt, 'wholStaaExcpPpltnCnt'), // 하수처리구역 외 합계 wholStaaOtsdPpltnCnt: formatNumber(d.wholStaaOtsdPpltnCnt, 'wholStaaOtsdPpltnCnt'), // 하수처리구역 외 합계
// 보급률(%) // 보급률(%)
staaPsplyRt: formatNumber(d.staaPsplyRt, 'staaPsplyRt'), // 공공하수처리구역 인구보급률 swagePplPsplyRt: formatNumber(d.swagePplPsplyRt, 'swagePplPsplyRt'), // 공공하수처리구역 인구보급률
altdPrcsPsplyRt: formatNumber(d.altdPrcsPsplyRt, 'altdPrcsPsplyRt'), // 고도처리 인구보급률 awtrPsplyRt: formatNumber(d.awtrPsplyRt, 'awtrPsplyRt'), // 고도처리 인구보급률
sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'), // 하수도 설치율 sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'), // 하수도 설치율
})); }));
const requestData: SewerageSupplyRateSaveRequest = { const requestData: SewerageSupplyRateSaveRequest = {
sewerBzmnCd: searchValues?.bsnmCd ?? '', // 하수도 사업자 코드 ...toInputSearchRegion(sidoListRef.current, searchValues),
ctpvNm: searchValues?.sido ?? '', // 시도 ctpvNm: searchValues?.sido ?? '', // 시도
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''),
statsYr: searchValues?.year ?? '', // 통계 연도 statsYr: searchValues?.year ?? '', // 통계 연도
userId: userInfo.userUnqId, // 사용자 userUuid: userInfo.userUnqId, // 사용자
saveList: convertedData, // 저장 데이터 saveList: convertedData, // 저장 데이터
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.sewerBzmnFrgnrWholPpltnCnt // 행정참고자료 외국인 합계 sewerBzmnArea: footerFromApi?.sewerBzmnArea, // 행정참고자료면적
}; };
if (isAdminInfo) { if (isAdminInfo) {
@@ -692,7 +733,7 @@ const SeweragePenetrationRateInp = () => {
if (response.status === 'success') { if (response.status === 'success') {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgress(100); setGlobalProgress(100);
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {});
alert('저장되었습니다.'); alert('저장되었습니다.');
} else { } else {
alert(response.message || '데이터 저장 중 오류가 발생했습니다.'); alert(response.message || '데이터 저장 중 오류가 발생했습니다.');
@@ -719,18 +760,17 @@ const SeweragePenetrationRateInp = () => {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: searchValues?.bsnmCd ?? '', ...toInputSearchRegion(sidoListRef.current, searchValues),
ctpvNm: searchValues?.sido ?? '', ctpvNm: searchValues?.sido ?? '',
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ""),
statsYr: searchValues?.year ?? '', statsYr: searchValues?.year ?? '',
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCd, statsCd: statsCd,
}; };
const response = await statisticsApi.cancelSubmit(requestData); const response = await statisticsApi.cancelSubmit(requestData);
if (response.status === 'success') { if (response.status === 'success') {
fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true); fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {});
alert('제출 취소되었습니다.'); alert('제출 취소되었습니다.');
} else { } else {
alert(response.message ?? '제출 취소 중 오류가 발생했습니다.'); alert(response.message ?? '제출 취소 중 오류가 발생했습니다.');
@@ -749,13 +789,14 @@ const SeweragePenetrationRateInp = () => {
setVerificationHistoryOpen(true); setVerificationHistoryOpen(true);
}; };
const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || '') const toolbarSearchContext = lastSearchedValues;
? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') const gugunOkOrNotDoList = isGugunOkForToolbar(sidoList, toolbarSearchContext);
: true; const submitOk = isSubmitOkForToolbar(sidoList, toolbarSearchContext);
const inputPeriodCurrent = isInputPeriodCurrent(periodCheckList); const inputPeriodCurrent = isInputPeriodCurrent(periodCheckList);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === '2'; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === '2';
const baseShowSaveInToolbar = (isEnterable || isAdminInfo) && gugunOkOrNotDoList; const baseShowSaveInToolbar =
(isEnterable || isAdminInfo) && inputPeriodCurrent && gugunOkOrNotDoList;
const baseShowEnterableToolbarButtons = isEnterable && gugunOkOrNotDoList; const baseShowEnterableToolbarButtons = isEnterable && gugunOkOrNotDoList;
const showSubmittedInInputPeriodToolbar = const showSubmittedInInputPeriodToolbar =
isSubmitted && inputPeriodCurrent && gugunOkOrNotDoList; isSubmitted && inputPeriodCurrent && gugunOkOrNotDoList;
@@ -773,6 +814,7 @@ const SeweragePenetrationRateInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
onSubmit={handleSearch} onSubmit={handleSearch}
@@ -789,10 +831,10 @@ const SeweragePenetrationRateInp = () => {
{showEnterableToolbarButtons && ( {showEnterableToolbarButtons && (
<> <>
<LastYearDataButton getLastYearDataFunc={() => getLastYearDataFunc(searchValues ?? {})} /> <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} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div> </div>
</div> </div>
@@ -805,9 +847,9 @@ const SeweragePenetrationRateInp = () => {
</div> </div>
} }
<DataGrid <DataGrid
key={(isEnterable || isAdminInfo) ? 'enterable' : 'readonly'} key={((isEnterable || isAdminInfo) && inputPeriodCurrent) ? 'enterable' : 'readonly'}
columnInfo={columnInfo} 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} excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
rowStartNum={3} rowStartNum={3}
@@ -826,6 +868,7 @@ const SeweragePenetrationRateInp = () => {
return hasImr ? 'rg-cell-verify-error-imr' : 'rg-cell-verify-error'; return hasImr ? 'rg-cell-verify-error-imr' : 'rg-cell-verify-error';
} }
}} }}
footerVerificationResult={footerVerificationResult}
cellTooltipCallback={({ row, fieldName }) => { cellTooltipCallback={({ row, fieldName }) => {
const verification = (row as GridRowWithVerification).verificationResult; const verification = (row as GridRowWithVerification).verificationResult;
if (!verification) return; if (!verification) return;
@@ -833,9 +876,9 @@ const SeweragePenetrationRateInp = () => {
if (Array.isArray(errors) && errors.length > 0) { if (Array.isArray(errors) && errors.length > 0) {
return errors return errors
.map((e) => { .map((e) => {
const prefix = e.vrfcCd === 'IMR' ? 'IMR검증 - ' : (e.vrfcCd === 'NV' ? '일반검증 - ' : 'HB검증 -'); const prefix = e.vrfcCd === 'IMR' ? '시계열 검증 - ' : (e.vrfcCd === 'NV' ? '일반검증 - ' : '모집단 검증 -');
return `${prefix}${e.rsnExpln} return `${prefix}${e.rsnExpln}
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; (검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join('\n'); .join('\n');
@@ -862,9 +905,8 @@ const SeweragePenetrationRateInp = () => {
queryContext={ queryContext={
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
? { ? {
sewerBzmnCd: searchValues.bsnmCd, ...toInputSearchRegion(sidoListRef.current, searchValues),
ctpvNm: searchValues.sido, ctpvNm: searchValues.sido,
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
statsYr: searchValues.year, statsYr: searchValues.year,
statsCd, statsCd,
} }
@@ -882,12 +924,8 @@ const SeweragePenetrationRateInp = () => {
queryContext={ queryContext={
searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd
? { ? {
sewerBzmnCd: searchValues.bsnmCd, ...toInputSearchRegion(sidoListRef.current, searchValues),
ctpvNm: searchValues.sido, ctpvNm: searchValues.sido,
sggNm:
searchValues.gugun === "ALL"
? ""
: (searchValues.gugun ?? ""),
statsYr: searchValues.year, statsYr: searchValues.year,
statsCd, statsCd,
} }
@@ -2,33 +2,13 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
import { getUserInfo } from "../../../../../../packages/common/components/src/api"; import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
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 { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; 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 { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
import { import {
SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD, SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD,
SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD, SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD,
@@ -39,20 +19,55 @@ import {
type SewerageServiceLevelResidentFriendlyFactSaveItem, type SewerageServiceLevelResidentFriendlyFactSaveItem,
type SewerageServiceLevelVerificationResult, type SewerageServiceLevelVerificationResult,
} from "@/entities/statistics/model/input/statistics-input-sewerage-service-level-interface"; } 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 { import type {
SewerageStatusInfo,
cancelSubmitRequest, cancelSubmitRequest,
ImrStatusResponse, ImrStatusResponse,
SewerageStatusInfo,
Sido,
} from "@/entities/statistics/model/statistics-interface"; } from "@/entities/statistics/model/statistics-interface";
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema"; import {
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; filterBySelectedAdmdstCd,
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; isGugunOkForToolbar,
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; isSubmitOkForToolbar,
import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; resolveNoApplicableForRequest,
import { useAdminStore } from "@/store/admin-store"; 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 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 = [ const SewerageServiceLevelInpTabList = [
ComplaintInpColumn, ComplaintInpColumn,
ResidentFriendlyFactInpColumn, ResidentFriendlyFactInpColumn,
@@ -60,11 +75,6 @@ const SewerageServiceLevelInpTabList = [
const firstTabKey = ComplaintInpColumn.tabKey; 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> = { const SEWERAGE_SERVICE_LEVEL_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY: Record<string, string> = {
[ComplaintInpColumn.tabKey]: SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD, [ComplaintInpColumn.tabKey]: SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD,
[ResidentFriendlyFactInpColumn.tabKey]: SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD, [ResidentFriendlyFactInpColumn.tabKey]: SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD,
@@ -109,8 +119,8 @@ const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined, statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean, periodEnterable: boolean,
): boolean => { ): boolean => {
if (statusInfo?.laeSbmsnSttsCd) { if (statusInfo?.lclgvSbmsnSttsCd) {
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
return false; return false;
} }
return periodEnterable; return periodEnterable;
@@ -118,10 +128,22 @@ const computeTabEnterable = (
return periodEnterable; return periodEnterable;
}; };
const sidoListRef: { current: Sido[] } = { current: [] };
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || "") return isGugunOkForToolbar(sidoListRef.current, values);
? Boolean(values?.gugun && values.gugun !== "ALL") }
: true;
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( function computeTabToolbarSnapshot(
@@ -134,8 +156,8 @@ function computeTabToolbarSnapshot(
): TabToolbarSnapshot { ): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext); const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable); const enterable = computeTabEnterable(statusInfo, periodEnterable);
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk; const showEnterableToolbarButtons = enterable && gugunOk;
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk; const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
const showActionToolbarCard = const showActionToolbarCard =
@@ -153,17 +175,16 @@ function buildSewerageServiceLevelSearchParam(
): SewerageServiceLevelComplaintRequestParams { ): SewerageServiceLevelComplaintRequestParams {
return { return {
statsYr: values.year, statsYr: values.year,
sewerBzmnCd: values.bsnmCd,
ctpvNm: values.sido, ctpvNm: values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun, ...toInputSearchRegion(sidoListRef.current, values),
...(overrides?.mode ? { mode: overrides.mode } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}),
}; };
} }
function mapServiceLevelListToGridRows< function mapServiceLevelListToGridRows<
T extends { verificationResult?: SewerageServiceLevelVerificationResult }, T extends { verificationResult?: SewerageServiceLevelVerificationResult },
>(list: T[], ctpvNm: string): GridRowWithVerification[] { >(list: T[], ctpvNm: string, values?: SearchFormValues | null): GridRowWithVerification[] {
return list.map((item) => { return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => {
const { verificationResult, ...rest } = item; const { verificationResult, ...rest } = item;
const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification;
if (verificationResult) row.verificationResult = verificationResult; 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( function buildColumnFormatNumber(
columns: { fieldName?: string; dataFormat?: string }[], columns: { fieldName?: string; dataFormat?: string }[],
) { ) {
@@ -210,21 +220,21 @@ function buildComplaintSaveList(
const formatNumber = buildColumnFormatNumber(ComplaintInpColumn.columns); const formatNumber = buildColumnFormatNumber(ComplaintInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
swtrmtPpltnCnt: formatNumber(d.swtrmtPpltnCnt, "swtrmtPpltnCnt"), swtrmtPpltnCnt: formatNumber(d.swtrmtPpltnCnt, "swtrmtPpltnCnt"),
dschgWqCnt: formatNumber(d.dschgWqCnt, "dschgWqCnt"), cvlcptDschgWq: formatNumber(d.cvlcptDschgWq, "cvlcptDschgWq"),
odorCnt: formatNumber(d.odorCnt, "odorCnt"), cvlcptOdorCnt: formatNumber(d.cvlcptOdorCnt, "cvlcptOdorCnt"),
ldsCnt: formatNumber(d.ldsCnt, "ldsCnt"), cvlcptDrgCnt: formatNumber(d.cvlcptDrgCnt, "cvlcptDrgCnt"),
plltnAcdntCnt: formatNumber(d.plltnAcdntCnt, "plltnAcdntCnt"), cvlcptMnhlCnt: formatNumber(d.cvlcptMnhlCnt, "cvlcptMnhlCnt"),
swagePplCnt: formatNumber(d.swagePplCnt, "swagePplCnt"), cvlcptSpctnkCnt: formatNumber(d.cvlcptSpctnkCnt, "cvlcptSpctnkCnt"),
sewerCrgAmt: formatNumber(d.sewerCrgAmt, "sewerCrgAmt"), cvlcptSewerCrg: formatNumber(d.cvlcptSewerCrg, "cvlcptSewerCrg"),
etcCnt: formatNumber(d.etcCnt, "etcCnt"), cvlcptEtcCnt: formatNumber(d.cvlcptEtcCnt, "cvlcptEtcCnt"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
whol: formatNumber(d.whol, "whol"), cvlcptWholCnt: formatNumber(d.cvlcptWholCnt, "cvlcptWholCnt"),
crtrPpltnCvlcptEvntcsCnt: formatNumber( crtrPpltnCvlcptCnt: formatNumber(
d.crtrPpltnCvlcptEvntcsCnt, d.crtrPpltnCvlcptCnt,
"crtrPpltnCvlcptEvntcsCnt", "crtrPpltnCvlcptCnt",
), ),
})); }));
} }
@@ -235,21 +245,21 @@ function buildResidentFriendlyFactSaveList(
const formatNumber = buildColumnFormatNumber(ResidentFriendlyFactInpColumn.columns); const formatNumber = buildColumnFormatNumber(ResidentFriendlyFactInpColumn.columns);
return currentData.map((d) => ({ return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd), admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: normalizeGridString(d.admdstNm), admdstNm: nullableTrimmedString(d.admdstNm),
swtrmtFcltCd: normalizeGridString(d.swtrmtFcltCd), swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
swtrmtFcltNm: normalizeGridString(d.swtrmtFcltNm), swtrmtFcltNm: nullableTrimmedString(d.swtrmtFcltNm),
fcltSn: normalizeGridString(d.fcltSn), fcltSn: nullableTrimmedString(d.fcltSn),
tmplntArea: formatNumber(d.tmplntArea, "tmplntArea"), stpar: formatNumber(d.stpar, "stpar"),
brkFcltArea: formatNumber(d.brkFcltArea, "brkFcltArea"), brkFcar: formatNumber(d.brkFcar, "brkFcar"),
childFrshpSpacArea: formatNumber(d.childFrshpSpacArea, "childFrshpSpacArea"), childFrshpSpacArea: formatNumber(d.childFrshpSpacArea, "childFrshpSpacArea"),
bdySportsFcltArea: formatNumber(d.bdySportsFcltArea, "bdySportsFcltArea"), bdySportsFcar: formatNumber(d.bdySportsFcar, "bdySportsFcar"),
sportsFcltArea: formatNumber(d.sportsFcltArea, "sportsFcltArea"), sportsFcar: formatNumber(d.sportsFcar, "sportsFcar"),
pkltArea: formatNumber(d.pkltArea, "pkltArea"), pkltar: formatNumber(d.pkltar, "pkltar"),
eduMultiFcltArea: formatNumber(d.eduMultiFcltArea, "eduMultiFcltArea"), eduMultiFcar: formatNumber(d.eduMultiFcar, "eduMultiFcar"),
wtlndMakeArea: formatNumber(d.wtlndMakeArea, "wtlndMakeArea"), wtlndMakeArea: formatNumber(d.wtlndMakeArea, "wtlndMakeArea"),
etcCnt: formatNumber(d.etcCnt, "etcCnt"), othrar: formatNumber(d.othrar, "othrar"),
rmrk: normalizeGridRmrk(d.rmrk), rmrk: nullableTrimmedString(d.rmrk),
})); }));
} }
@@ -271,15 +281,17 @@ async function waitForImrJobCompleted(
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); 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 === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { 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); await sleep(pollIntervalMs);
} }
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
} }
// 하수도 통계 관리 > 13. 하수도 서비스 수준 // 하수도 통계 관리 > 13. 하수도 서비스 수준
const SewerageServiceLevelInp = () => { const SewerageServiceLevelInp = () => {
const { sidoList } = useSearchConditionSidoList({ isInput: true });
sidoListRef.current = sidoList;
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
@@ -307,9 +319,15 @@ const SewerageServiceLevelInp = () => {
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
Map<string, boolean> Map<string, boolean>
>(new Map()); >(new Map());
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< const [footerVerificationByTab, setFooterVerificationByTab] = useState<
Map<string, VerificationResultLikeMap | null>
>(new Map());
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
Map<string, TabToolbarSnapshot> Map<string, TabToolbarSnapshot>
>(new Map()); >(new Map());
const [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
() => new Map(),
);
const [columnGuidesByTab, setColumnGuidesByTab] = useState< const [columnGuidesByTab, setColumnGuidesByTab] = useState<
Record<string, Record<string, string>> Record<string, Record<string, string>>
>({}); >({});
@@ -326,42 +344,30 @@ const SewerageServiceLevelInp = () => {
const publicSewageFacilitiesAvailableRef = useRef(false); const publicSewageFacilitiesAvailableRef = useRef(false);
activeTabRef.current = activeTab; activeTabRef.current = activeTab;
const activeTabId = useTabStore((s) => s.activeTabId);
useEffect(() => { 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; let cancelled = false;
void (async () => { void (async () => {
const entries = await Promise.all( const next = await fetchColumnGuidesRecord(statsCd);
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;
}
}),
);
if (cancelled) return; if (cancelled) return;
const guidesByStatsCd = Object.fromEntries(entries); setColumnGuidesByTab((prev) =>
setColumnGuidesByTab( Object.prototype.hasOwnProperty.call(prev, tabKey)
Object.fromEntries( ? prev
Object.entries(SEWERAGE_SERVICE_LEVEL_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY).map( : { ...prev, [tabKey]: next },
([tabKey, statsCd]) => [tabKey, guidesByStatsCd[statsCd] ?? {}],
),
),
); );
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, [activeTabId, activeTab, columnGuidesByTab]);
const commitToolbarSnapshotForLoadedTab = ( const commitToolbarSnapshotForLoadedTab = (
tabKey: string, tabKey: string,
@@ -384,6 +390,13 @@ const SewerageServiceLevelInp = () => {
); );
return next; return next;
}); });
if (searchContext) {
setSearchContextByTab((prev) => {
const next = new Map(prev);
next.set(tabKey, searchContext);
return next;
});
}
}; };
const applyRowMap = ( const applyRowMap = (
@@ -425,6 +438,7 @@ const SewerageServiceLevelInp = () => {
const gridRows = mapServiceLevelListToGridRows( const gridRows = mapServiceLevelListToGridRows(
(complaintList ?? []) as SewerageServiceLevelComplaintItem[], (complaintList ?? []) as SewerageServiceLevelComplaintItem[],
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
applyRowMap(mergeIntoExistingMap, (m) => { applyRowMap(mergeIntoExistingMap, (m) => {
@@ -435,6 +449,9 @@ const SewerageServiceLevelInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
setSearchLoaded(true); setSearchLoaded(true);
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
@@ -454,6 +471,7 @@ const SewerageServiceLevelInp = () => {
const gridRows = mapServiceLevelListToGridRows( const gridRows = mapServiceLevelListToGridRows(
(residentFriendlyFactList ?? []) as SewerageServiceLevelResidentFriendlyFactItem[], (residentFriendlyFactList ?? []) as SewerageServiceLevelResidentFriendlyFactItem[],
commonInfo.ctpvNm, commonInfo.ctpvNm,
searchContext,
); );
if (!fromPreYear) { if (!fromPreYear) {
publicSewageFacilitiesAvailableRef.current = gridRows.length > 0; publicSewageFacilitiesAvailableRef.current = gridRows.length > 0;
@@ -470,6 +488,9 @@ const SewerageServiceLevelInp = () => {
setIsVerificationFailedByTab((prev) => setIsVerificationFailedByTab((prev) =>
patchVerificationFailedByTab(prev, tabKey, commonInfo), patchVerificationFailedByTab(prev, tabKey, commonInfo),
); );
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
);
if (updateLastSearchedValues) setLastSearchedValues(searchContext); if (updateLastSearchedValues) setLastSearchedValues(searchContext);
setSearchLoaded(true); setSearchLoaded(true);
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
@@ -493,7 +514,9 @@ const SewerageServiceLevelInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setLastSearchedValues(null); setLastSearchedValues(null);
@@ -538,7 +561,9 @@ const SewerageServiceLevelInp = () => {
setTabStatusInfoMap(new Map()); setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map()); setTabStatsCdMap(new Map());
setIsVerificationFailedByTab(new Map()); setIsVerificationFailedByTab(new Map());
setFooterVerificationByTab(new Map());
setToolbarSnapshotByTab(new Map()); setToolbarSnapshotByTab(new Map());
setSearchContextByTab(new Map());
setIsNotApplicable(false); setIsNotApplicable(false);
activeTabRef.current = firstTabKey; activeTabRef.current = firstTabKey;
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
@@ -657,9 +682,19 @@ const SewerageServiceLevelInp = () => {
const tabKey = activeTabRef.current; const tabKey = activeTabRef.current;
const isResidentTab = tabKey === ResidentFriendlyFactInpColumn.tabKey; const isResidentTab = tabKey === ResidentFriendlyFactInpColumn.tabKey;
const latestRows = getTabGridRows(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 (latestRows.length === 0) {
if (isResidentTab && !noApplicable) { if (
isResidentTab &&
!noApplicable &&
buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)
) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -700,7 +735,13 @@ const SewerageServiceLevelInp = () => {
return next; return next;
}); });
const noApplicable = isResidentTab && isNotApplicable; const noApplicable =
isResidentTab &&
buildNoApplicableForRequest(
searchContextByTab.get(tabKey) ?? lastSearchedValues,
isNotApplicable,
latestRows.length === 0,
);
const complaintSubmitList = buildComplaintSaveList(latestRows); const complaintSubmitList = buildComplaintSaveList(latestRows);
const residentSubmitList = noApplicable const residentSubmitList = noApplicable
? [] ? []
@@ -710,7 +751,12 @@ const SewerageServiceLevelInp = () => {
alert("제출할 데이터가 없습니다."); alert("제출할 데이터가 없습니다.");
return; return;
} }
if (isResidentTab && !noApplicable && residentSubmitList.length === 0) { if (
isResidentTab &&
!noApplicable &&
residentSubmitList.length === 0 &&
buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)
) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -725,28 +771,20 @@ const SewerageServiceLevelInp = () => {
const submitInfoBase = { const submitInfoBase = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL" lclgvPicNm: submitInfo.lclgvPicNm,
? "" lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
: (lastSearchedValues.gugun ?? ""), lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
laePicNm: submitInfo.laePicNm, lclgvIdfrNm: submitInfo.lclgvIdfrNm,
laePicTelNo: submitInfo.laePicTelNo, lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
}; };
const verificationQuery = { const verificationQuery = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -769,13 +807,13 @@ const SewerageServiceLevelInp = () => {
const response = isComplaintTab const response = isComplaintTab
? await statisticsInputSewerageServiceLevelApi.submitComplaintList({ ? await statisticsInputSewerageServiceLevelApi.submitComplaintList({
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
submitList: complaintSubmitList, submitList: complaintSubmitList,
}) })
: await statisticsInputSewerageServiceLevelApi.submitResidentFriendlyFactList({ : await statisticsInputSewerageServiceLevelApi.submitResidentFriendlyFactList({
...submitInfoBase, ...submitInfoBase,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
noApplicable, noApplicable,
submitList: residentSubmitList, submitList: residentSubmitList,
@@ -791,14 +829,15 @@ const SewerageServiceLevelInp = () => {
if (finalStatus === "5") { if (finalStatus === "5") {
setGlobalProgress(50); setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중..."); setGlobalProgressLabel("시계열 검증 진행 중...");
const imrParam = { const imrParam = {
user_id: userInfo.userUnqId, user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
stats_cd: statsCdForTab, stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? "", 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", gen_verify_stts_cd: nvpassed ? "1" : "2",
hb_pass_yn: response.data?.isHbPassed,
}; };
try { try {
@@ -875,15 +914,17 @@ const SewerageServiceLevelInp = () => {
return next; return next;
}); });
const noApplicable = isResidentTab && isNotApplicable; const noApplicable =
isResidentTab &&
buildNoApplicableForRequest(
searchContextByTab.get(tabKey) ?? lastSearchedValues,
isNotApplicable,
latestRows.length === 0,
);
const baseRequest = { const baseRequest = {
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm: ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
}; };
try { try {
@@ -900,7 +941,7 @@ const SewerageServiceLevelInp = () => {
const response = const response =
await statisticsInputSewerageServiceLevelApi.saveComplaintList({ await statisticsInputSewerageServiceLevelApi.saveComplaintList({
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
saveList, saveList,
}); });
@@ -913,7 +954,11 @@ const SewerageServiceLevelInp = () => {
const saveList = noApplicable const saveList = noApplicable
? [] ? []
: buildResidentFriendlyFactSaveList(latestRows); : buildResidentFriendlyFactSaveList(latestRows);
if (!noApplicable && saveList.length === 0) { if (
!noApplicable &&
saveList.length === 0 &&
buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)
) {
alert("해당사항 없음을 체크해주세요."); alert("해당사항 없음을 체크해주세요.");
return; return;
} }
@@ -922,7 +967,7 @@ const SewerageServiceLevelInp = () => {
await statisticsInputSewerageServiceLevelApi.saveResidentFriendlyFactList( await statisticsInputSewerageServiceLevelApi.saveResidentFriendlyFactList(
{ {
...baseRequest, ...baseRequest,
userId: userInfo.userUnqId, userUuid: userInfo.userUnqId,
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
noApplicable, noApplicable,
saveList, saveList,
@@ -973,14 +1018,10 @@ const SewerageServiceLevelInp = () => {
} }
const requestData: cancelSubmitRequest = { const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", ...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
ctpvNm: lastSearchedValues.sido ?? "", ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
statsYr: lastSearchedValues.year ?? "", statsYr: lastSearchedValues.year ?? "",
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
statsCd: statsCdForTab, statsCd: statsCdForTab,
}; };
@@ -1016,10 +1057,10 @@ const SewerageServiceLevelInp = () => {
for (const tab of SewerageServiceLevelInpTabList) { for (const tab of SewerageServiceLevelInpTabList) {
const tabKey = tab.tabKey; const tabKey = tab.tabKey;
const canEdit = const canEdit =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const rawColumns = (tab.columns ?? []) as ColumnConfig[]; const rawColumns = (tab.columns ?? []) as ColumnConfig[];
const base = canEdit const base = canEdit
? rawColumns ? rawColumns
@@ -1036,15 +1077,15 @@ const SewerageServiceLevelInp = () => {
return map; return map;
}, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]); }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]);
const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const activeTabRowData = rowDataMap.get(activeTab) ?? []; const activeTabRowData = rowDataMap.get(activeTab) ?? [];
const activeTabToolbar = const activeTabToolbar =
toolbarSnapshotByTab.get(activeTab) ??
computeTabToolbarSnapshot( computeTabToolbarSnapshot(
activeTabStatus, activeTabStatus,
tabRowDataExists(rowDataMap, activeTab), tabRowDataExists(rowDataMap, activeTab),
hasSearched, hasSearched,
lastSearchedValues, searchContextForToolbar,
inputPeriodEnterable, inputPeriodEnterable,
isAdminInfo, isAdminInfo,
); );
@@ -1055,12 +1096,14 @@ const SewerageServiceLevelInp = () => {
activeTabToolbar.showEnterableToolbarButtons && toolbarGroupAllowed; activeTabToolbar.showEnterableToolbarButtons && toolbarGroupAllowed;
const showActionToolbarCard = const showActionToolbarCard =
activeTabToolbar.showActionToolbarCard && toolbarGroupAllowed; activeTabToolbar.showActionToolbarCard && toolbarGroupAllowed;
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
const showNotApplicableForResidentTab = const showNotApplicableForResidentTab =
activeTab === ResidentFriendlyFactInpColumn.tabKey && activeTab === ResidentFriendlyFactInpColumn.tabKey &&
searchLoaded && searchLoaded &&
hasSearched && hasSearched &&
activeTabRowData.length === 0 && activeTabRowData.length === 0 &&
computeTabEnterable(activeTabStatus, inputPeriodEnterable); computeTabEnterable(activeTabStatus, inputPeriodEnterable) &&
submitOk;
const showSecondaryToolbarCard = const showSecondaryToolbarCard =
hasSearched && hasSearched &&
toolbarGroupAllowed && toolbarGroupAllowed &&
@@ -1071,9 +1114,8 @@ const SewerageServiceLevelInp = () => {
const statsCd = tabStatsCdMap.get(activeTab); const statsCd = tabStatsCdMap.get(activeTab);
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return { return {
sewerBzmnCd: ctx.bsnmCd, ...toInputSearchRegion(sidoListRef.current, ctx),
ctpvNm: ctx.sido, ctpvNm: ctx.sido,
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
statsYr: ctx.year, statsYr: ctx.year,
statsCd, statsCd,
}; };
@@ -1110,11 +1152,11 @@ const SewerageServiceLevelInp = () => {
.map((e) => { .map((e) => {
const prefix = const prefix =
e.vrfcCd === "IMR" e.vrfcCd === "IMR"
? "IMR검증 - " ? "시계열 검증 - "
: e.vrfcCd === "NV" : e.vrfcCd === "NV"
? "일반검증 - " ? "일반검증 - "
: "HB검증 - "; : "모집단 검증 - ";
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
}) })
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
@@ -1136,6 +1178,7 @@ const SewerageServiceLevelInp = () => {
<> <>
<div className="relative"> <div className="relative">
<SearchForm <SearchForm
isInput
key={searchFormRemountKey} key={searchFormRemountKey}
defaultValues={verificationSearchDefaults ?? undefined} defaultValues={verificationSearchDefaults ?? undefined}
onSubmit={handleSearch} onSubmit={handleSearch}
@@ -1143,8 +1186,8 @@ const SewerageServiceLevelInp = () => {
onValuesChange={setSearchValues} onValuesChange={setSearchValues}
/> />
{showActionToolbarCard && ( {showActionToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]"> <div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<VerificationResultButton <VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog} VerificationResultButtonFunc={openVerificationHistoryDialog}
isVerificationFailed={ isVerificationFailed={
@@ -1156,19 +1199,19 @@ const SewerageServiceLevelInp = () => {
<LastYearDataButton <LastYearDataButton
getLastYearDataFunc={() => void getLastYearDataFunc()} getLastYearDataFunc={() => void getLastYearDataFunc()}
/> />
<SubmitButton submitFunc={submitFunc} /> {submitOk && <SubmitButton submitFunc={submitFunc} />}
</> </>
)} )}
{activeTabStatus?.laeSbmsnSttsCd === "2" && ( {activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} /> <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)} )}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />} {showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div> </div>
</div> </div>
)} )}
{showSecondaryToolbarCard && ( {showSecondaryToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]"> <div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
{showNotApplicableForResidentTab && ( {showNotApplicableForResidentTab && (
<NotApplicableCheckButton <NotApplicableCheckButton
isCheck={isNotApplicable} isCheck={isNotApplicable}
@@ -1203,11 +1246,11 @@ const SewerageServiceLevelInp = () => {
const tabKey = info.tabKey; const tabKey = info.tabKey;
const rowData = rowDataMap.get(tabKey) ?? []; const rowData = rowDataMap.get(tabKey) ?? [];
const tabEnterableForGrid = const tabEnterableForGrid =
computeTabEnterable( (computeTabEnterable(
tabStatusInfoMap.get(tabKey) ?? null, tabStatusInfoMap.get(tabKey) ?? null,
inputPeriodEnterable, inputPeriodEnterable,
) || isAdminInfo; ) || isAdminInfo) && inputPeriodEnterable;
const gugunOk = buildGugunOkForToolbar(lastSearchedValues); const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
return ( return (
@@ -1245,6 +1288,9 @@ const SewerageServiceLevelInp = () => {
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
isExcelExport isExcelExport
cellStyleCallback={verificationCellStyle} cellStyleCallback={verificationCellStyle}
footerVerificationResult={
footerVerificationByTab.get(tabKey) ?? null
}
cellTooltipCallback={verificationCellTooltip} cellTooltipCallback={verificationCellTooltip}
getGridDataRef={ getGridDataRef={
activeTab === tabKey ? getGridDataRef : undefined activeTab === tabKey ? getGridDataRef : undefined
@@ -1,13 +1,14 @@
import { useState } from "react"; 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 DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import type { ColumnConfig } from "@/components/type/DataGrid.type"; import type { ColumnConfig } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { toGridRows } from "@/components/util/grid/gridFunction"; 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 StatisticsReviewHistory = () => {
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
@@ -69,7 +70,7 @@ const StatisticsReviewHistory = () => {
return ( return (
<div className="relative"> <div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} /> <SearchForm isInput onSubmit={handleSearch} onReset={handleReset} />
{isLoading && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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]"> <div className="relative size-[5rem]">
@@ -4,32 +4,33 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { RealGridReact } from "realgrid-react"; import { RealGridReact } from "realgrid-react";
import useSWR, { useSWRConfig } from "swr"; 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 { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
import { StatisticsInputStatusApi } from "@/entities/statistics/api/input/statistics-input-status-service"; import { StatisticsInputStatusApi } from "@/entities/statistics/api/input/statistics-input-status-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-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 { import type {
StatsInputStatusListApiResponse, StatsInputStatusListApiResponse,
StatsInputStatusListRequestParams, StatsInputStatusListRequestParams,
} from "@/entities/statistics/model/input/statistics-input-status-interface"; } 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 { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { LoadingWithAnimation } from "@common/components";
import ApproveButton from "@/components/button/ApproveButton"; import ApproveButton from "@/components/button/ApproveButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type";
import { import {
ExceptionProcessingHistoryDialog, ExceptionProcessingHistoryDialog,
type ExceptionProcessingHistoryQueryContext, type ExceptionProcessingHistoryQueryContext,
} from "@/components/module/ExceptionProcessingHistoryDialog"; } from "@/components/module/ExceptionProcessingHistoryDialog";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.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 { StatsInputStatusInpColumn } from "@/components/util/column/input/StatsInputStatusInpColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { useTabStore } from "@/store/tab-store"; import { useTabStore } from "@/store/tab-store";
@@ -43,6 +44,7 @@ const getStatsInputStatusFetcher = async (
// 하수도 통계 관리 > 00. 통계 입력 현황 // 하수도 통계 관리 > 00. 통계 입력 현황
const StatsInputStatusInp = () => { const StatsInputStatusInp = () => {
const { sidoList } = useSearchConditionSidoList();
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const activeTabId = useTabStore((state) => state.activeTabId); const activeTabId = useTabStore((state) => state.activeTabId);
@@ -174,8 +176,8 @@ const StatsInputStatusInp = () => {
setIsApproveLoading(true); setIsApproveLoading(true);
try { try {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const mdfrId = (userInfo.userUnqId ?? "").trim(); const mdfrUuid = (userInfo.userUnqId ?? "").trim();
if (!mdfrId) { if (!mdfrUuid) {
alert("수정자 ID를 확인할 수 없습니다."); alert("수정자 ID를 확인할 수 없습니다.");
return; return;
} }
@@ -187,7 +189,7 @@ const StatsInputStatusInp = () => {
.map((row) => { .map((row) => {
const statsCd = String(row.statsCd ?? "").trim(); const statsCd = String(row.statsCd ?? "").trim();
const sewerBzmnCd = String(row.sewerBzmnCd ?? sewerBzmnCdDefault).trim(); const sewerBzmnCd = String(row.sewerBzmnCd ?? sewerBzmnCdDefault).trim();
return { statsYr, sewerBzmnCd, statsCd, mdfrId }; return { statsYr, sewerBzmnCd, statsCd, mdfrUuid };
}) })
.filter((item) => item.statsCd.length > 0); .filter((item) => item.statsCd.length > 0);
@@ -253,9 +255,7 @@ const StatsInputStatusInp = () => {
await mutate(["stats-input-status", searchParams]); await mutate(["stats-input-status", searchParams]);
}, [mutate, searchParams]); }, [mutate, searchParams]);
const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || "") const gugunOkOrNotDoList = isGugunOkForToolbar(sidoList, lastSearchedValues);
? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== "ALL")
: true;
const allRowsLaeSbmsnApproved = useMemo( const allRowsLaeSbmsnApproved = useMemo(
() => () =>
@@ -1,11 +1,12 @@
"use client"; "use client";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm, Controller } from "react-hook-form"; import { Controller,useForm } 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 { 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 { import {
Button, Button,
Dialog, Dialog,
@@ -14,12 +15,12 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
Input, Input,
LoadingWithAnimation,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableHead, TableHead,
TableRow, TableRow,
LoadingWithAnimation,
} from "@common/components"; } from "@common/components";
/** 숫자만 추출 후 하이픈 포맷 (010-1234-5678, 02-123-4567 등) */ /** 숫자만 추출 후 하이픈 포맷 (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)}`; 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 { interface SubmitDialogProps {
open: boolean; open: boolean;
@@ -55,26 +56,26 @@ interface SubmitDialogProps {
} }
const defaultValues: StatisticsInputSubmitInfoFormData = { const defaultValues: StatisticsInputSubmitInfoFormData = {
laePicNm: "", lclgvPicNm: "",
laeKbrdrJbgdNm: "", lclgvKbrdrJbgdNm: "",
laeKbrdrDeptNm: "", lclgvKbrdrDeptNm: "",
laePicTelNo: "", lclgvPicTelnoCn: "",
laeIdfrNm: "", lclgvIdfrNm: "",
laeIdfrJbgdNm: "", lclgvIdfrJbgdNm: "",
laeIdfrDeptNm: "", lclgvIdfrDeptNm: "",
laeIdfrTelNo: "", lclgvIdfrTelnoCn: "",
}; };
function statusInfoToFormData(info: SewerageStatusInfo): StatisticsInputSubmitInfoFormData { function statusInfoToFormData(info: SewerageStatusInfo): StatisticsInputSubmitInfoFormData {
return { return {
laePicNm: info.laePicNm ?? "", lclgvPicNm: info.lclgvPicNm ?? "",
laeKbrdrJbgdNm: info.laeKbrdrJbgdNm ?? "", lclgvKbrdrJbgdNm: info.lclgvKbrdrJbgdNm ?? "",
laeKbrdrDeptNm: info.laeKbrdrDeptNm ?? "", lclgvKbrdrDeptNm: info.lclgvKbrdrDeptNm ?? "",
laePicTelNo: info.laePicTelNo ?? "", lclgvPicTelnoCn: info.lclgvPicTelnoCn ?? "",
laeIdfrNm: info.laeIdfrNm ?? "", lclgvIdfrNm: info.lclgvIdfrNm ?? "",
laeIdfrJbgdNm: info.laeIdfrJbgdNm ?? "", lclgvIdfrJbgdNm: info.lclgvIdfrJbgdNm ?? "",
laeIdfrDeptNm: info.laeIdfrDeptNm ?? "", lclgvIdfrDeptNm: info.lclgvIdfrDeptNm ?? "",
laeIdfrTelNo: info.laeIdfrTelNo ?? "", lclgvIdfrTelnoCn: info.lclgvIdfrTelnoCn ?? "",
}; };
}; };
@@ -114,14 +115,14 @@ const SubmitDailog = ({
} }
const rows: { name: keyof StatisticsInputSubmitInfoFormData; label: string; placeholder: string }[] = [ const rows: { name: keyof StatisticsInputSubmitInfoFormData; label: string; placeholder: string }[] = [
{ name: "laePicNm", label: "작성자 명", placeholder: "작성자 명 입력" }, { name: "lclgvPicNm", label: "작성자 명", placeholder: "작성자 명 입력" },
{ name: "laeKbrdrJbgdNm", label: "작성자 직제", placeholder: "작성자 직제 입력" }, { name: "lclgvKbrdrJbgdNm", label: "작성자 직제", placeholder: "작성자 직제 입력" },
{ name: "laeKbrdrDeptNm", label: "작성자 부서", placeholder: "작성자 부서 입력" }, { name: "lclgvKbrdrDeptNm", label: "작성자 부서", placeholder: "작성자 부서 입력" },
{ name: "laePicTelNo", label: "작성자 연락처", placeholder: "작성자 연락처 입력" }, { name: "lclgvPicTelnoCn", label: "작성자 연락처", placeholder: "작성자 연락처 입력" },
{ name: "laeIdfrNm", label: "승인자명", placeholder: "승인자명 입력" }, { name: "lclgvIdfrNm", label: "승인자명", placeholder: "승인자명 입력" },
{ name: "laeIdfrJbgdNm", label: "승인자 직제", placeholder: "승인자 직제 입력" }, { name: "lclgvIdfrJbgdNm", label: "승인자 직제", placeholder: "승인자 직제 입력" },
{ name: "laeIdfrDeptNm", label: "승인자 부서", placeholder: "승인자 부서 입력" }, { name: "lclgvIdfrDeptNm", label: "승인자 부서", placeholder: "승인자 부서 입력" },
{ name: "laeIdfrTelNo", label: "승인자 연락처", placeholder: "승인자 연락처" }, { name: "lclgvIdfrTelnoCn", label: "승인자 연락처", placeholder: "승인자 연락처" },
]; ];
return ( return (
File diff suppressed because it is too large Load Diff
@@ -1,15 +1,15 @@
import { Button } from "@common/components"; 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 type { useAdmdstRenewalTab } from "@/components/statistics/management/hooks/useAdmdstRenewalTab";
import { asGridRowData, asGridRowSetter, fromGridRow } from "@/components/statistics/management/lib/gridCast"; import { asGridRowData, asGridRowSetter, fromGridRow } from "@/components/statistics/management/lib/gridCast";
import type { AdmdstRenewalAddedItem } from "@/components/statistics/management/types/population-tab-types"; 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 = { const reducedColumnInfo: ColumnInfo = {
columns: AdmdstRenewalReducedColumnConfig.columns ?? [], columns: AdmdstRenewalReducedColumnConfig.columns ?? [],
@@ -1,17 +1,19 @@
import { Button } from "@common/components"; import { useRef } from "react";
import type { RealGridReact } from "realgrid-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 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 type { ColumnInfo, GridViewWithEditCommit } from "@/components/type/DataGrid.type";
import { import {
DomesticPopulationLeftColumnConfig, DomesticPopulationLeftColumnConfig,
DomesticPopulationRightColumnConfig, DomesticPopulationRightColumnConfig,
} from "@/components/util/column/management/DomesticPopulationColumn"; } 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 = { 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 { useRef } from "react";
import type { RealGridReact } from "realgrid-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 { import {
ForeignPopulationLeftColumnConfig, ForeignPopulationLeftColumnConfig,
ForeignPopulationRightColumnConfig, ForeignPopulationRightColumnConfig,
} from "@/components/util/column/management/ForeignPopulationColumn"; } 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 = { const leftColumnInfo: ColumnInfo = {
columns: ForeignPopulationLeftColumnConfig.columns ?? [], columns: ForeignPopulationLeftColumnConfig.columns ?? [],
columnLayout: ForeignPopulationLeftColumnConfig.columnLayout ?? [], columnLayout: ForeignPopulationLeftColumnConfig.columnLayout ?? [],
@@ -130,9 +130,9 @@ const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
}; };
const getSggListFetcher = async ( const getSggListFetcher = async (
key: [string, string], key: [string, string, "Y" | "N"],
): Promise<ApiResponse<Sgg[]>> => { ): Promise<ApiResponse<Sgg[]>> => {
return statisticsApi.getSggList(key[1]); return statisticsApi.getSggList(key[1], { gugunSkipYn: key[2] });
}; };
const LogHistory = () => { const LogHistory = () => {
@@ -214,8 +214,14 @@ const LogHistory = () => {
}, },
); );
const sggKey: [string, string] | null = const selectedSido = sidoList?.data?.sidoList?.find(
sido && sido !== "ALL" ? ["log-history-sgg-list", sido] : null; (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[]>>( const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
sggKey, sggKey,
getSggListFetcher, getSggListFetcher,
@@ -1,8 +1,12 @@
"use client"; "use client";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { GridFitStyle } from "realgrid"; import { GridFitStyle } from "realgrid";
import { useAlertStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { UserApi } from "@/entities/user";
import { import {
Button, Button,
Dialog, Dialog,
@@ -14,6 +18,8 @@ import {
LoadingWithAnimation, LoadingWithAnimation,
} from "@common/components"; } from "@common/components";
import { LogHistoryDetailsNestedDialog } from "./LogHistoryDetailsNestedDialog";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import type { import type {
ColumnInfo, ColumnInfo,
@@ -25,15 +31,10 @@ import {
emptyLogHistoryDetailColumnInfo, emptyLogHistoryDetailColumnInfo,
LOG_DETAIL_DETAILS_FIELD, LOG_DETAIL_DETAILS_FIELD,
LOG_DETAIL_DETAILS_RAW_FIELD, LOG_DETAIL_DETAILS_RAW_FIELD,
normalizeLogDetailList,
type LogDetailVerificationItem, type LogDetailVerificationItem,
normalizeLogDetailList,
prepareLogDetailRowsForGrid, prepareLogDetailRowsForGrid,
} from "@/components/util/column/management/LogHistoryDetailColumn"; } 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 { export interface LogHistoryDetailDialogProps {
open: boolean; open: boolean;
@@ -74,9 +75,9 @@ export function LogHistoryDetailDialog({
if (!row) return; if (!row) return;
const sn = row.statsLogSn; const sn = row.statsLogSn;
const rgtrId = const rgtrUuid =
row.rgtrId != null && String(row.rgtrId).trim() !== "" row.rgtrUuid != null && String(row.rgtrUuid).trim() !== ""
? String(row.rgtrId).trim() ? String(row.rgtrUuid).trim()
: ""; : "";
let cancelled = false; let cancelled = false;
@@ -87,8 +88,8 @@ export function LogHistoryDetailDialog({
try { try {
const userLookupPromise = const userLookupPromise =
rgtrId !== "" rgtrUuid !== ""
? UserApi.findByUserUnqId(rgtrId).catch(() => { ? UserApi.findByUserUnqId(rgtrUuid).catch(() => {
return null; return null;
}) })
: Promise.resolve(null); : Promise.resolve(null);
@@ -48,7 +48,7 @@ export function LogHistoryDetailsNestedDialog({
<span className="font-semibold"> <span className="font-semibold">
조합순번: 조합순번:
</span>{" "} </span>{" "}
{item.eqtnCombSeq ?? "-"} {item.fmlaCombSeq ?? "-"}
</p> </p>
<p> <p>
<span className="font-semibold"> <span className="font-semibold">
@@ -60,7 +60,7 @@ export function LogHistoryDetailsNestedDialog({
<span className="font-semibold"> <span className="font-semibold">
비교연산: 비교연산:
</span>{" "} </span>{" "}
{item.cmprOpert ?? "-"} {item.cmprArtopnCn ?? "-"}
</p> </p>
<p> <p>
<span className="font-semibold"> <span className="font-semibold">
@@ -72,13 +72,13 @@ export function LogHistoryDetailsNestedDialog({
<span className="font-semibold"> <span className="font-semibold">
한글 수식: 한글 수식:
</span>{" "} </span>{" "}
{item.kornArtopnEqtnCn ?? "-"} {item.kornArtopnFmlaCn ?? "-"}
</p> </p>
<p className="col-span-2"> <p className="col-span-2">
<span className="font-semibold"> <span className="font-semibold">
영문 수식: 영문 수식:
</span>{" "} </span>{" "}
{item.engArtopnEqtnCn ?? "-"} {item.engArtopnFmlaCn ?? "-"}
</p> </p>
</div> </div>
</div> </div>
@@ -5,8 +5,8 @@ import { GridFitStyle } from "realgrid";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { import type {
UnSubmitDataStatusItem,
unSubmitDataRequest, unSubmitDataRequest,
UnSubmitDataStatusItem,
} from "@/entities/statistics/model/statistics-interface"; } from "@/entities/statistics/model/statistics-interface";
import { LoadingWithAnimation } from "@common/components"; import { LoadingWithAnimation } from "@common/components";
@@ -279,7 +279,6 @@ const NotEnteredStatus = () => {
return; return;
} }
const params = buildUnSubmitDataRequest(values, userInfo); const params = buildUnSubmitDataRequest(values, userInfo);
console.log(params);
if (!params.dtlInstCd) { if (!params.dtlInstCd) {
alert("기관 정보를 확인할 수 없습니다."); alert("기관 정보를 확인할 수 없습니다.");
return; return;
@@ -1,18 +1,19 @@
import { Chart } from "react-chartjs-2"; import { Chart } from "react-chartjs-2";
import type { ChartOptions } from "chart.js";
import { import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarController, BarController,
BarElement, BarElement,
CategoryScale,
Chart as ChartJS,
Legend,
LinearScale,
LineController, LineController,
LineElement, LineElement,
PointElement, PointElement,
Title, Title,
Tooltip, Tooltip,
Legend,
} from "chart.js"; } from "chart.js";
import type { ChartOptions } from "chart.js";
ChartJS.register( ChartJS.register(
CategoryScale, CategoryScale,
@@ -1,9 +1,8 @@
import DataGrid from "@/components/module/DataGrid"; 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 type { usePopulationStatusTab } from "@/components/statistics/management/hooks/usePopulationStatusTab";
import { asGridRowData, asGridRowSetter } from "@/components/statistics/management/lib/gridCast"; 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 = { const columnInfo: ColumnInfo = {
columns: PopulationStatusColumnConfig.columns ?? [], columns: PopulationStatusColumnConfig.columns ?? [],
@@ -1,4 +1,5 @@
import PopulationStatusChart from "./PopulationStatusChart"; import PopulationStatusChart from "./PopulationStatusChart";
import type { usePopulationVizTab } from "@/components/statistics/management/hooks/usePopulationVizTab"; import type { usePopulationVizTab } from "@/components/statistics/management/hooks/usePopulationVizTab";
type Props = { 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 { useAlertStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; 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"; import { toGridRows } from "@/components/util/grid/gridFunction";
/** API `YYYYMMDD` 등 → Date */ /** API `YYYYMMDD` 등 → Date */
function seTypeToDisplayLabel(seType: StatsInputPeriodItem["seType"]): string { function seTypeToDisplayLabel(seTypeCd: StatsInputPeriodItem["seTypeCd"]): string {
if (seType === "INPT") return "입력"; if (seTypeCd === "INPT") return "입력";
if (seType === "VRFC") return "검증"; if (seTypeCd === "VRFC") return "검증";
return seType; return seTypeCd;
} }
function rowSeTypeToCode(row: Record<string, string | number>): "INPT" | "VRFC" { 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 === "INPT" || v === "입력") return "INPT";
if (v === "VRFC" || v === "검증") return "VRFC"; if (v === "VRFC" || v === "검증") return "VRFC";
return "INPT"; return "INPT";
@@ -86,16 +86,16 @@ const StatisticsInputManagement = () => {
const params: StatsInputPeriodListRequest = { const params: StatsInputPeriodListRequest = {
statsYr: values.year, statsYr: values.year,
...(values.clsfCd && values.clsfCd !== "ALL" ...(values.clsfCd && values.clsfCd !== "ALL"
? { seType: values.clsfCd } ? { seTypeCd: values.clsfCd }
: {}), : {}),
}; };
const res = await statisticsApi.getStatsInputPeriodList(params); const res = await statisticsApi.getStatsInputPeriodList(params);
if (res.status === "success" && Array.isArray(res.data)) { if (res.status === "success" && Array.isArray(res.data)) {
const rowsForGrid = res.data.map((row) => ({ const rowsForGrid = res.data.map((row) => ({
...row, ...row,
seType: seTypeToDisplayLabel(row.seType), seTypeCd: seTypeToDisplayLabel(row.seTypeCd),
inptBgngYmd: formatYmdForDisplay(row.inptBgngYmd), bgngYmd: formatYmdForDisplay(row.bgngYmd),
inptEndYmd: formatYmdForDisplay(row.inptEndYmd), endYmd: formatYmdForDisplay(row.endYmd),
[PERIOD_DETAIL_FIELD]: "상세정보", [PERIOD_DETAIL_FIELD]: "상세정보",
})); }));
setRowData(toGridRows(rowsForGrid)); setRowData(toGridRows(rowsForGrid));
@@ -115,6 +115,14 @@ const StatisticsInputManagement = () => {
setRowData([]); setRowData([]);
}; };
useEffect(() => {
const defaultYear = String(new Date().getFullYear() - 1);
void handleSearch({
year: defaultYear,
clsfCd: "ALL",
});
}, []);
const handleRegisterClick = () => { const handleRegisterClick = () => {
const yr = const yr =
searchFormValues?.year ?? lastSearchRef.current?.year ?? ""; searchFormValues?.year ?? lastSearchRef.current?.year ?? "";
@@ -130,22 +138,22 @@ const StatisticsInputManagement = () => {
const openPeriodDetailForRow = useCallback( const openPeriodDetailForRow = useCallback(
async (row: Record<string, string | number>) => { async (row: Record<string, string | number>) => {
const seCdRaw = row.seCd; const cyclSeNoRaw = row.cyclSeNo;
const seCdNum = const cyclSeNoNum =
typeof seCdRaw === "number" typeof cyclSeNoRaw === "number"
? seCdRaw ? cyclSeNoRaw
: parseInt(String(seCdRaw ?? ""), 10); : parseInt(String(cyclSeNoRaw ?? ""), 10);
const seCd = Number.isFinite(seCdNum) ? seCdNum : 0; const cyclSeNo = Number.isFinite(cyclSeNoNum) ? cyclSeNoNum : 0;
const statsYr = String(row.statsYr ?? ""); const statsYr = String(row.statsYr ?? "");
const seType = rowSeTypeToCode(row); const seTypeCd = rowSeTypeToCode(row);
const extSchdlId = parseExtSchdlId(row.extSchdlId); const extSchdlId = parseExtSchdlId(row.extSchdlId);
try { try {
setIsLoading(true); setIsLoading(true);
const res = await statisticsApi.getStatsInputPeriodDetail({ const res = await statisticsApi.getStatsInputPeriodDetail({
statsYr, statsYr,
seType, seTypeCd,
seCd, cyclSeNo,
...(extSchdlId != null ? { extSchdlId } : {}), ...(extSchdlId != null ? { extSchdlId } : {}),
}); });
@@ -160,13 +168,13 @@ const StatisticsInputManagement = () => {
setPeriodDialogInitial({ setPeriodDialogInitial({
extSchdlId: detailExtSchdlId, extSchdlId: detailExtSchdlId,
statsYr: String(detail.statsYr ?? statsYr), statsYr: String(detail.statsYr ?? statsYr),
seType: detail.seType === "VRFC" ? "VRFC" : "INPT", seTypeCd: detail.seTypeCd === "VRFC" ? "VRFC" : "INPT",
seCd: cyclSeNo:
typeof detail.seCd === "number" && Number.isFinite(detail.seCd) typeof detail.cyclSeNo === "number" && Number.isFinite(detail.cyclSeNo)
? detail.seCd ? detail.cyclSeNo
: seCd, : cyclSeNo,
inptBgngYmd: parseYmdToDate(detail.inptBgngYmd), bgngYmd: parseYmdToDate(detail.bgngYmd),
inptEndYmd: parseYmdToDate(detail.inptEndYmd), endYmd: parseYmdToDate(detail.endYmd),
}); });
setPeriodDialogOpen(true); setPeriodDialogOpen(true);
} catch { } catch {
@@ -194,20 +202,20 @@ const StatisticsInputManagement = () => {
editable: false, editable: false,
}, },
{ {
name: "seType", name: "seTypeCd",
fieldName: "seType", fieldName: "seTypeCd",
header: { text: "분류" }, header: { text: "분류" },
editable: false, editable: false,
}, },
{ {
name: "inptBgngYmd", name: "bgngYmd",
fieldName: "inptBgngYmd", fieldName: "bgngYmd",
header: { text: "시작일" }, header: { text: "시작일" },
editable: false, editable: false,
}, },
{ {
name: "inptEndYmd", name: "endYmd",
fieldName: "inptEndYmd", fieldName: "endYmd",
header: { text: "종료일" }, header: { text: "종료일" },
editable: false, editable: false,
}, },
@@ -223,9 +231,9 @@ const StatisticsInputManagement = () => {
const columnLayout = [ const columnLayout = [
"statsYr", "statsYr",
"seType", "seTypeCd",
"inptBgngYmd", "bgngYmd",
"inptEndYmd", "endYmd",
PERIOD_DETAIL_FIELD, PERIOD_DETAIL_FIELD,
]; ];
@@ -33,10 +33,10 @@ import { getUserInfo } from "../../../../../../packages/common/components/src/ap
export type StatisticsInputPeriodDialogValues = { export type StatisticsInputPeriodDialogValues = {
extSchdlId?: number; extSchdlId?: number;
statsYr: string; statsYr: string;
seType: "INPT" | "VRFC"; seTypeCd: "INPT" | "VRFC";
seCd: number; cyclSeNo: number;
inptBgngYmd: Date | undefined; bgngYmd: Date | undefined;
inptEndYmd: Date | undefined; endYmd: Date | undefined;
}; };
export type StatisticsInputPeriodDialogMode = "edit" | "create"; export type StatisticsInputPeriodDialogMode = "edit" | "create";
@@ -76,33 +76,33 @@ export function StatisticsInputPeriodDialog({
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const [statsYr, setStatsYr] = useState(""); const [statsYr, setStatsYr] = useState("");
const [seType, setSeType] = useState<"INPT" | "VRFC" | null>(null); const [seTypeCd, setSeTypeCd] = useState<"INPT" | "VRFC" | null>(null);
const [seCdInput, setSeCdInput] = useState(""); const [cyclSeNoInput, setCyclSeNoInput] = useState("");
const [inptBgngYmd, setInptBgngYmd] = useState<Date | undefined>(undefined); const [bgngYmd, setBgngYmd] = useState<Date | undefined>(undefined);
const [inptEndYmd, setInptEndYmd] = useState<Date | undefined>(undefined); const [endYmd, setEndYmd] = useState<Date | undefined>(undefined);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => { useEffect(() => {
if (!open) return; if (!open) return;
if (mode === "create") { if (mode === "create") {
setStatsYr(normalizeStatsYrInput(createDefaultStatsYr ?? "")); setStatsYr(normalizeStatsYrInput(createDefaultStatsYr ?? ""));
setSeType(null); setSeTypeCd(null);
setSeCdInput("0"); setCyclSeNoInput("0");
setInptBgngYmd(undefined); setBgngYmd(undefined);
setInptEndYmd(undefined); setEndYmd(undefined);
return; return;
} }
if (!initial) return; if (!initial) return;
setStatsYr(normalizeStatsYrInput(String(initial.statsYr ?? ""))); setStatsYr(normalizeStatsYrInput(String(initial.statsYr ?? "")));
setSeType(initial.seType); setSeTypeCd(initial.seTypeCd);
setSeCdInput(String(Number.isFinite(initial.seCd) ? initial.seCd : 0)); setCyclSeNoInput(String(Number.isFinite(initial.cyclSeNo) ? initial.cyclSeNo : 0));
setInptBgngYmd(initial.inptBgngYmd); setBgngYmd(initial.bgngYmd);
setInptEndYmd(initial.inptEndYmd); setEndYmd(initial.endYmd);
}, [open, mode, initial, createDefaultStatsYr]); }, [open, mode, initial, createDefaultStatsYr]);
const handleSave = async () => { const handleSave = async () => {
const bgng = formatDateToYmd(inptBgngYmd); const bgng = formatDateToYmd(bgngYmd);
const end = formatDateToYmd(inptEndYmd); const end = formatDateToYmd(endYmd);
if (!bgng || !end) { if (!bgng || !end) {
alert("시작일과 종료일을 선택해주세요."); alert("시작일과 종료일을 선택해주세요.");
return; return;
@@ -111,18 +111,18 @@ export function StatisticsInputPeriodDialog({
alert("시작일은 종료일보다 늦을 수 없습니다."); alert("시작일은 종료일보다 늦을 수 없습니다.");
return; return;
} }
if (seType === null) { if (seTypeCd === null) {
alert("분류를 선택해주세요."); alert("분류를 선택해주세요.");
return; return;
} }
const seCdParsed = const cyclSeNoParsed =
mode === "create" mode === "create"
? 0 ? 0
: (() => { : (() => {
const n = parseInt(seCdInput, 10); const n = parseInt(cyclSeNoInput, 10);
return Number.isFinite(n) && n >= 0 ? n : NaN; return Number.isFinite(n) && n >= 0 ? n : NaN;
})(); })();
if (mode === "edit" && !Number.isFinite(seCdParsed)) { if (mode === "edit" && !Number.isFinite(cyclSeNoParsed)) {
alert("구분 값이 올바르지 않습니다."); alert("구분 값이 올바르지 않습니다.");
return; return;
} }
@@ -140,13 +140,13 @@ export function StatisticsInputPeriodDialog({
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const param: StatsInputPeriodSaveRequest = { const param: StatsInputPeriodSaveRequest = {
statsYr: yr, statsYr: yr,
seType, seTypeCd,
inptBgngYmd: bgng, bgngYmd: bgng,
inptEndYmd: end, endYmd: end,
rgtrId: userInfo.userUnqId, rgtrUuid: userInfo.userUnqId,
}; };
if (!isCreate) { if (!isCreate) {
param.seCd = seCdParsed; param.cyclSeNo = cyclSeNoParsed;
if ( if (
initial?.extSchdlId != null && initial?.extSchdlId != null &&
Number.isFinite(initial.extSchdlId) Number.isFinite(initial.extSchdlId)
@@ -173,8 +173,8 @@ export function StatisticsInputPeriodDialog({
if (mode === "create" || !initial) return; if (mode === "create" || !initial) return;
if (!(await confirm("삭제하시겠습니까?"))) return; if (!(await confirm("삭제하시겠습니까?"))) return;
const bgng = formatDateToYmd(initial.inptBgngYmd); const bgng = formatDateToYmd(initial.bgngYmd);
const end = formatDateToYmd(initial.inptEndYmd); const end = formatDateToYmd(initial.endYmd);
if (!bgng || !end) { if (!bgng || !end) {
alert("삭제할 기간 정보가 없습니다."); alert("삭제할 기간 정보가 없습니다.");
return; return;
@@ -184,19 +184,19 @@ export function StatisticsInputPeriodDialog({
alert("연도가 없습니다."); alert("연도가 없습니다.");
return; return;
} }
const seCd = const cyclSeNo =
Number.isFinite(initial.seCd) && initial.seCd >= 0 ? initial.seCd : 0; Number.isFinite(initial.cyclSeNo) && initial.cyclSeNo >= 0 ? initial.cyclSeNo : 0;
try { try {
setIsSubmitting(true); setIsSubmitting(true);
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const param: StatsInputPeriodDeleteRequest = { const param: StatsInputPeriodDeleteRequest = {
statsYr: yr, statsYr: yr,
seType: initial.seType, seTypeCd: initial.seTypeCd,
seCd, cyclSeNo,
inptBgngYmd: bgng, bgngYmd: bgng,
inptEndYmd: end, endYmd: end,
rgtrId: userInfo.userUnqId, rgtrUuid: userInfo.userUnqId,
}; };
if (initial.extSchdlId != null && Number.isFinite(initial.extSchdlId)) { if (initial.extSchdlId != null && Number.isFinite(initial.extSchdlId)) {
param.extSchdlId = initial.extSchdlId; param.extSchdlId = initial.extSchdlId;
@@ -253,8 +253,8 @@ export function StatisticsInputPeriodDialog({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Label className="text-label">분류</Label> <Label className="text-label">분류</Label>
<Select <Select
value={seType ?? undefined} value={seTypeCd ?? undefined}
onValueChange={(v) => setSeType(v as "INPT" | "VRFC")} onValueChange={(v) => setSeTypeCd(v as "INPT" | "VRFC")}
disabled={isSubmitting || mode === "edit"} disabled={isSubmitting || mode === "edit"}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
@@ -273,7 +273,7 @@ export function StatisticsInputPeriodDialog({
type="number" type="number"
inputMode="numeric" inputMode="numeric"
min={0} min={0}
value={seCdInput} value={cyclSeNoInput}
disabled disabled
className="!text-[1.3rem]" className="!text-[1.3rem]"
/> />
@@ -282,16 +282,16 @@ export function StatisticsInputPeriodDialog({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Label className="text-label">시작일</Label> <Label className="text-label">시작일</Label>
<InputDatePicker <InputDatePicker
value={inptBgngYmd} value={bgngYmd}
onChange={setInptBgngYmd} onChange={setBgngYmd}
className="w-full" className="w-full"
/> />
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Label className="text-label">종료일</Label> <Label className="text-label">종료일</Label>
<InputDatePicker <InputDatePicker
value={inptEndYmd} value={endYmd}
onChange={setInptEndYmd} onChange={setEndYmd}
className="w-full" className="w-full"
/> />
</div> </div>
@@ -1,23 +1,22 @@
import { useRef, useState } from "react"; 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 { statisticsPpltnApi } from "@/entities/statistics/api/statistics-ppltn-service";
import type { DomesticPopulationItem } from "@/entities/statistics/model/ppltn/statistics-domestic-population-interface"; 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 AdmdstRenewalTab from "./AdmdstRenewalTab";
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 DomesticPopulationTab from "./DomesticPopulationTab"; import DomesticPopulationTab from "./DomesticPopulationTab";
import ForeignPopulationTab from "./ForeignPopulationTab"; import ForeignPopulationTab from "./ForeignPopulationTab";
import AdmdstRenewalTab from "./AdmdstRenewalTab";
import PopulationStatusTab from "./PopulationStatusTab"; import PopulationStatusTab from "./PopulationStatusTab";
import PopulationVizTab from "./PopulationVizTab"; 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 { ForeignPopulationItem } from "@/components/statistics/management/types/population-tab-types";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
const TAB_LIST = [ const TAB_LIST = [
{ key: "domestic", label: "행정인구등록" }, { key: "domestic", label: "행정인구등록" },
@@ -1,4 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { import {
Button, Button,
Dialog, Dialog,
@@ -2,7 +2,8 @@ import { useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib"; import { useAlertStore } from "@/shared/lib";
import { statisticsPpltnApi } from "@/entities/statistics/api/statistics-ppltn-service"; 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단계 상위) 였음. // TODO: 원본 파일은 `../../../../../../packages/common/components/src/api` (6단계 상위) 였음.
// 이 훅 파일은 원본 컴포넌트보다 한 단계 더 깊은 폴더(hooks/)에 위치하므로 // 이 훅 파일은 원본 컴포넌트보다 한 단계 더 깊은 폴더(hooks/)에 위치하므로
// 실제 프로젝트에 옮길 때 상대경로 깊이를 다시 맞춰줘야 함 (혹은 @common/components에 getUserInfo가 // 실제 프로젝트에 옮길 때 상대경로 깊이를 다시 맞춰줘야 함 (혹은 @common/components에 getUserInfo가
@@ -10,13 +11,14 @@ import { toGridRows } from "@/components/util/grid/gridFunction";
import { getUserInfo } from "../../../../../../../packages/common/components/src/api"; import { getUserInfo } from "../../../../../../../packages/common/components/src/api";
import { useAsyncAction } from "./useAsyncAction"; import { useAsyncAction } from "./useAsyncAction";
import type { UpperCodeCandidateItem } from "@/entities/statistics/model/ppltn/statistics-domestic-population-interface";
import type { import type {
AdmdstRenewalAddedItem, AdmdstRenewalAddedItem,
AdmdstRenewalAddedSaveItem, AdmdstRenewalAddedSaveItem,
AdmdstRenewalReducedItem, AdmdstRenewalReducedItem,
AdmdstRenewalReducedSaveItem, AdmdstRenewalReducedSaveItem,
} from "@/components/statistics/management/types/population-tab-types"; } from "@/components/statistics/management/types/population-tab-types";
import { toGridRows } from "@/components/util/grid/gridFunction";
export function useAdmdstRenewalTab() { export function useAdmdstRenewalTab() {
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
@@ -1,15 +1,17 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import * as XLSX from "xlsx"; import * as XLSX from "xlsx";
import { useAlertStore } from "@/shared/lib"; import { useAlertStore } from "@/shared/lib";
import { toGridRows } from "@/components/util/grid/gridFunction";
import { useAsyncAction } from "./useAsyncAction"; import { useAsyncAction } from "./useAsyncAction";
import type { import type {
PopulationMappingApiConfig, PopulationMappingApiConfig,
PopulationMappingRow, PopulationMappingRow,
UpperCodeCandidateItem, UpperCodeCandidateItem,
} from "@/components/statistics/management/types/population-tab-types"; } from "@/components/statistics/management/types/population-tab-types";
import { toGridRows } from "@/components/util/grid/gridFunction";
/* ── 엑셀 헤더 검증 (원본 컴포넌트의 validateExcelHeader를 그대로 이동) ── */ /* ── 엑셀 헤더 검증 (원본 컴포넌트의 validateExcelHeader를 그대로 이동) ── */
const validateExcelHeader = (file: File, requiredKeywords: string[]): Promise<boolean> => { const validateExcelHeader = (file: File, requiredKeywords: string[]): Promise<boolean> => {
@@ -2,10 +2,12 @@ import { useState } from "react";
import { useAlertStore } from "@/shared/lib"; import { useAlertStore } from "@/shared/lib";
import { statisticsPpltnApi } from "@/entities/statistics/api/statistics-ppltn-service"; 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 { useAsyncAction } from "./useAsyncAction";
import type { PopulationStatusItem } from "../types/population-tab-types";
import { toGridRows } from "@/components/util/grid/gridFunction";
export function usePopulationStatusTab() { export function usePopulationStatusTab() {
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
@@ -3,9 +3,10 @@ import { useState } from "react";
import { useAlertStore } from "@/shared/lib"; import { useAlertStore } from "@/shared/lib";
import { statisticsPpltnApi } from "@/entities/statistics/api/statistics-ppltn-service"; import { statisticsPpltnApi } from "@/entities/statistics/api/statistics-ppltn-service";
import { useAsyncAction } from "./useAsyncAction";
import type { PopulationChartData } from "../PopulationStatusChart"; import type { PopulationChartData } from "../PopulationStatusChart";
import { useAsyncAction } from "./useAsyncAction";
export function usePopulationVizTab() { export function usePopulationVizTab() {
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { isLoading, run } = useAsyncAction(alert); 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 { 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 { useMemo, useState } from "react";
import { LoadingWithAnimation } from "@common/components";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { import type {
ExcretaTreatmentFacilityReviewItem, ExcretaTreatmentFacilityReviewItem,
} from "@/entities/statistics/model/statistics-excreta-treatment-facility-review-interface"; } from "@/entities/statistics/model/statistics-excreta-treatment-facility-review-interface";
@@ -11,28 +10,29 @@ import {
extractExcretaTreatmentFacilityReviewList, extractExcretaTreatmentFacilityReviewList,
extractExcretaTreatmentFacilityReviewStatsStatusInfo, extractExcretaTreatmentFacilityReviewStatsStatusInfo,
} from "@/entities/statistics/model/statistics-excreta-treatment-facility-review-interface"; } from "@/entities/statistics/model/statistics-excreta-treatment-facility-review-interface";
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
import type { import type {
ReviewOpinionSaveRequest,
ReviewerInfoRequest, ReviewerInfoRequest,
ReviewOpinionSaveRequest,
} from "@/entities/statistics/model/statistics-interface"; } 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 ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { ExcretaTreatmentFacilityInfoList } from "@/components/util/column/review/ExcretaTreatmentFacilityRvwColumn"; import { ExcretaTreatmentFacilityInfoList } from "@/components/util/column/review/ExcretaTreatmentFacilityRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
const excretaTreatmentFacilityTab = ExcretaTreatmentFacilityInfoList[0]; const excretaTreatmentFacilityTab = ExcretaTreatmentFacilityInfoList[0];
@@ -76,11 +76,11 @@ const ExcretaTreatmentFacilityRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -150,16 +150,16 @@ const ExcretaTreatmentFacilityRvw = () => {
...item, ...item,
admdstNm: item.admdstNm ?? "", admdstNm: item.admdstNm ?? "",
fcltPstnCn: item.fcltPstnCn ?? "", fcltPstnCn: item.fcltPstnCn ?? "",
telNo: item.telNo ?? "", telnoCn: item.telnoCn ?? "",
cmptnStrtYmd: item.cmptnStrtYmd ?? "", cmptnStrtYmd: item.cmptnStrtYmd ?? "",
oprtngStrtYmd: item.oprtngStrtYmd ?? "", oprtngStrtYmd: item.oprtngStrtYmd ?? "",
prcsMthdExpln: item.prcsMthdExpln ?? "", prcsExpln: item.prcsExpln ?? "",
linkTmplntCn: item.linkTmplntCn ?? "", linkTmplntCn: item.linkTmplntCn ?? "",
wtsysNm: item.wtsysNm ?? "", fcltWtsysNm: item.fcltWtsysNm ?? "",
trbtrNm: item.trbtrNm ?? "", fcltTrbtrNm: item.fcltTrbtrNm ?? "",
msirbCn: item.msirbCn ?? "", msirbCn: item.msirbCn ?? "",
ssirbCn: item.ssirbCn ?? "", ssirbCn: item.ssirbCn ?? "",
operMthdCdCn: item.operMthdCdCn ?? "", operCn: item.operCn ?? "",
rmrk: item.rmrk ?? "", rmrk: item.rmrk ?? "",
})); }));
@@ -170,7 +170,7 @@ const ExcretaTreatmentFacilityRvw = () => {
statsCd: statsInfo.statsCd ?? "", statsCd: statsInfo.statsCd ?? "",
}); });
setLatestReviewOpinion(statsInfo.splmntOpnnCn ?? ""); setLatestReviewOpinion(statsInfo.splmntOpnnCn ?? "");
setLatestProcessHistory(statsInfo.wrterOpnnCn ?? ""); setLatestProcessHistory(statsInfo.splmntRsltCn ?? "");
setRowData(toGridRows(convertedData as unknown as object[])); setRowData(toGridRows(convertedData as unknown as object[]));
} else { } else {
setRowData([]); setRowData([]);
@@ -216,7 +216,7 @@ const ExcretaTreatmentFacilityRvw = () => {
const row = detailRes.data?.[0]; const row = detailRes.data?.[0];
setLatestReviewOpinion(row?.splmntOpnnCn ?? ""); setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
setLatestProcessHistory(row?.wrterOpnnCn ?? ""); setLatestProcessHistory(row?.splmntRsltCn ?? "");
setReviewOpinionOpen(true); setReviewOpinionOpen(true);
} catch { } catch {
alert("검토의견 조회 중 오류가 발생했습니다."); alert("검토의견 조회 중 오류가 발생했습니다.");
@@ -234,10 +234,10 @@ const ExcretaTreatmentFacilityRvw = () => {
setStatsStatusInfo((prev) => ({ setStatsStatusInfo((prev) => ({
...prev, ...prev,
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
})); }));
setLatestReviewOpinion(param.splmntOpnnCn); setLatestReviewOpinion(param.splmntOpnnCn);
setLatestProcessHistory(param.wrterOpnnCn); setLatestProcessHistory(param.splmntRsltCn);
} catch { } catch {
alert("저장 중 오류가 발생했습니다."); alert("저장 중 오류가 발생했습니다.");
} }
@@ -3,6 +3,10 @@ import { useMemo, useRef, useState } from "react";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
ReviewerInfoRequest,
ReviewOpinionSaveRequest,
} from "@/entities/statistics/model/statistics-interface";
import { import {
extractPrivateSewageFacilityReviewList, extractPrivateSewageFacilityReviewList,
extractPrivateSewageFacilityReviewStatsStatusInfo, extractPrivateSewageFacilityReviewStatsStatusInfo,
@@ -10,29 +14,25 @@ import {
type PrivateSewageFacilityReviewRequestParams, type PrivateSewageFacilityReviewRequestParams,
} from "@/entities/statistics/model/statistics-private-sewage-facility-review-interface"; } from "@/entities/statistics/model/statistics-private-sewage-facility-review-interface";
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { PrivateSewageFacilityInfoListRvw } from "@/components/util/column/review/PrivateSewageFacilityColumn"; import { PrivateSewageFacilityInfoListRvw } from "@/components/util/column/review/PrivateSewageFacilityColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
const firstTabKey = PrivateSewageFacilityInfoListRvw[0]?.tabKey ?? "ST00080001"; const firstTabKey = PrivateSewageFacilityInfoListRvw[0]?.tabKey ?? "ST00080001";
@@ -102,7 +102,7 @@ const PrivateSewageFacilityRvw = () => {
}, [activeStatsStatusInfo.statsCd, lastSearchValues]); }, [activeStatsStatusInfo.statsCd, lastSearchValues]);
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? ""; const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? ""; const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => { const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (lastSearchValues == null) return null; if (lastSearchValues == null) return null;
@@ -127,11 +127,11 @@ const PrivateSewageFacilityRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = activeStatsStatusInfo; const statsStatusInfo = activeStatsStatusInfo;
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -284,7 +284,7 @@ const PrivateSewageFacilityRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: row?.splmntOpnnCn ?? "", splmntOpnnCn: row?.splmntOpnnCn ?? "",
wrterOpnnCn: row?.wrterOpnnCn ?? "", splmntRsltCn: row?.splmntRsltCn ?? "",
}); });
return next; return next;
}); });
@@ -307,7 +307,7 @@ const PrivateSewageFacilityRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
}); });
return next; return next;
}); });
@@ -1,32 +1,32 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { LoadingWithAnimation } from "@common/components";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-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 { 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 { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
import type { import { LoadingWithAnimation } from "@common/components";
ReviewOpinionSaveRequest,
ReviewerInfoRequest, import ReturnButton from "@/components/button/ReturnButton";
} from "@/entities/statistics/model/statistics-interface"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; 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 { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { PublicSewageFacilityInfoList } from "@/components/util/column/review/PublicSewageFacilityRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { PublicSewageFacilityInfoList } from "@/components/util/column/review/PublicSewageFacilityRvwColumn";
// 하수도 통계 관리 06.공공하수처리시설 // 하수도 통계 관리 06.공공하수처리시설
const PublicSewageFacilityRvw = () => { const PublicSewageFacilityRvw = () => {
@@ -63,8 +63,8 @@ const PublicSewageFacilityRvw = () => {
); );
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const submit = statsStatusInfo.submitStatusCd != null ? String(statsStatusInfo.submitStatusCd) : ""; const submit = statsStatusInfo.lclgvSbmsnSttsCd != null ? String(statsStatusInfo.lclgvSbmsnSttsCd) : "";
const prvn = statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; const prvn = statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -144,7 +144,7 @@ const PublicSewageFacilityRvw = () => {
response.data.statsStatusInfo.splmntOpnnCn ?? "", response.data.statsStatusInfo.splmntOpnnCn ?? "",
); );
setLatestProcessHistory( setLatestProcessHistory(
response.data.statsStatusInfo.wrterOpnnCn ?? "", response.data.statsStatusInfo.splmntRsltCn ?? "",
); );
setRowData(toGridRows(convertedData as unknown as object[])); setRowData(toGridRows(convertedData as unknown as object[]));
} else { } else {
@@ -192,7 +192,7 @@ const PublicSewageFacilityRvw = () => {
const row = detailRes.data?.[0]; const row = detailRes.data?.[0];
setLatestReviewOpinion(row?.splmntOpnnCn ?? ""); setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
setLatestProcessHistory(row?.wrterOpnnCn ?? ""); setLatestProcessHistory(row?.splmntRsltCn ?? "");
setReviewOpinionOpen(true); setReviewOpinionOpen(true);
} catch { } catch {
alert("검토의견 조회 중 오류가 발생했습니다."); alert("검토의견 조회 중 오류가 발생했습니다.");
@@ -210,10 +210,10 @@ const PublicSewageFacilityRvw = () => {
setStatsStatusInfo((prev) => ({ setStatsStatusInfo((prev) => ({
...prev, ...prev,
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
})); }));
setLatestReviewOpinion(param.splmntOpnnCn); setLatestReviewOpinion(param.splmntOpnnCn);
setLatestProcessHistory(param.wrterOpnnCn); setLatestProcessHistory(param.splmntRsltCn);
} catch { } catch {
alert("저장 중 오류가 발생했습니다."); alert("저장 중 오류가 발생했습니다.");
} }
@@ -1,36 +1,36 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { LoadingWithAnimation } from "@common/components";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-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 { import {
extractMediationPumpStationList, extractMediationPumpStationList,
extractMediationPumpStationStatsStatusInfo, extractMediationPumpStationStatsStatusInfo,
type MediationPumpStationItem, type MediationPumpStationItem,
} from "@/entities/statistics/model/statistics-pumping-station-review-interface"; } from "@/entities/statistics/model/statistics-pumping-station-review-interface";
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface"; import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
import type { import { LoadingWithAnimation } from "@common/components";
ReviewOpinionSaveRequest,
ReviewerInfoRequest,
} from "@/entities/statistics/model/statistics-interface";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { PumpingStationInfoList } from "@/components/util/column/review/PumpingStationRvwColumn"; import { PumpingStationInfoList } from "@/components/util/column/review/PumpingStationRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
/** /**
* 하수도 통계 검토 05.중계펌프장 * 하수도 통계 검토 05.중계펌프장
@@ -71,11 +71,11 @@ const PumpingStationRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -144,12 +144,9 @@ const PumpingStationRvw = () => {
const convertedData = rawList.map((item: MediationPumpStationItem) => ({ const convertedData = rawList.map((item: MediationPumpStationItem) => ({
...item, ...item,
admdstNm: item.admdstNm ?? "", admdstNm: item.admdstNm ?? "",
pumpstNm: item.fcltNm ?? "",
impdFcltPstnCn: item.fcltPstnCn ?? "",
pumpSeNm: item.pumpSeNm ?? "", pumpSeNm: item.pumpSeNm ?? "",
swtrmtFcltNm: item.swtrmtFcltNm ?? "", swtrmtFcltNm: item.swtrmtFcltNm ?? "",
oprtngStrtYmd: item.oprtngStrtYmd ?? "", oprtngStrtYmd: item.oprtngStrtYmd ?? "",
exlnPumpN4RsrvCnt: item.exlnPumpN1RsrvCnt ?? 0,
rmrk: item.rmrk ?? "", rmrk: item.rmrk ?? "",
})); }));
@@ -160,7 +157,7 @@ const PumpingStationRvw = () => {
statsCd: statsInfo.statsCd ?? "", statsCd: statsInfo.statsCd ?? "",
}); });
setLatestReviewOpinion(statsInfo.splmntOpnnCn ?? ""); setLatestReviewOpinion(statsInfo.splmntOpnnCn ?? "");
setLatestProcessHistory(statsInfo.wrterOpnnCn ?? ""); setLatestProcessHistory(statsInfo.splmntRsltCn ?? "");
setRowData(toGridRows(convertedData)); setRowData(toGridRows(convertedData));
} else { } else {
setRowData([]); setRowData([]);
@@ -206,7 +203,7 @@ const PumpingStationRvw = () => {
const row = detailRes.data?.[0]; const row = detailRes.data?.[0];
setLatestReviewOpinion(row?.splmntOpnnCn ?? ""); setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
setLatestProcessHistory(row?.wrterOpnnCn ?? ""); setLatestProcessHistory(row?.splmntRsltCn ?? "");
setReviewOpinionOpen(true); setReviewOpinionOpen(true);
} catch { } catch {
alert("검토의견 조회 중 오류가 발생했습니다."); alert("검토의견 조회 중 오류가 발생했습니다.");
@@ -224,10 +221,10 @@ const PumpingStationRvw = () => {
setStatsStatusInfo((prev) => ({ setStatsStatusInfo((prev) => ({
...prev, ...prev,
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
})); }));
setLatestReviewOpinion(param.splmntOpnnCn); setLatestReviewOpinion(param.splmntOpnnCn);
setLatestProcessHistory(param.wrterOpnnCn); setLatestProcessHistory(param.splmntRsltCn);
} catch { } catch {
alert("저장 중 오류가 발생했습니다."); alert("저장 중 오류가 발생했습니다.");
} }
@@ -1,9 +1,12 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; 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 { import {
Button, Button,
Dialog, Dialog,
@@ -16,9 +19,7 @@ import {
Textarea, Textarea,
} from "@common/components"; } from "@common/components";
import type { ReviewReturnSaveRequest } from "@/entities/statistics/model/statistics-interface"; import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { useAlertStore } from "@/shared/lib";
export type ReturnReasonDialogContext = Pick< export type ReturnReasonDialogContext = Pick<
ReviewReturnSaveRequest, ReviewReturnSaveRequest,
@@ -95,7 +96,7 @@ export function ReturnReasonDialog({
sewerBzmnCd: context.sewerBzmnCd, sewerBzmnCd: context.sewerBzmnCd,
statsCd: context.statsCd, statsCd: context.statsCd,
rjctOpnnCn: reason, rjctOpnnCn: reason,
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
}; };
const response = await statisticsApi.saveReviewReturn(param); const response = await statisticsApi.saveReviewReturn(param);
@@ -2,6 +2,8 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useAlertStore } from "@/shared/lib";
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface";
import { import {
Button, Button,
Dialog, Dialog,
@@ -13,10 +15,8 @@ import {
Label, Label,
Textarea, Textarea,
} from "@common/components"; } from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface"; import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { useAlertStore } from "@/shared/lib";
export type ReviewOpinionDialogContext = Pick< export type ReviewOpinionDialogContext = Pick<
ReviewOpinionSaveRequest, ReviewOpinionSaveRequest,
@@ -96,14 +96,14 @@ export function ReviewOpinionDialog({
sewerBzmnCd: context.sewerBzmnCd, sewerBzmnCd: context.sewerBzmnCd,
statsCd: context.statsCd, statsCd: context.statsCd,
splmntOpnnCn: reviewOpinion, splmntOpnnCn: reviewOpinion,
wrterOpnnCn: processHistory, splmntRsltCn: processHistory,
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
}; };
await onSave?.(param); await onSave?.(param);
resetForm(); resetForm();
onOpenChange(false); onOpenChange(false);
} catch { } catch {
/* 저장 실패 시 부모에서 alert 후 throw — 다이얼로그는 유지 */ console.error('검토 의견 저장 error');
} finally { } finally {
setIsSaving(false); setIsSaving(false);
} }
@@ -1,8 +1,14 @@
"use client"; "use client";
import { useEffect, useState } from "react"; 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 { import {
Button, Button,
Dialog, Dialog,
@@ -16,19 +22,18 @@ import {
Textarea, Textarea,
} from "@common/components"; } from "@common/components";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import type {
ReviewerInfoRequest,
ReviewerInfoResponse,
ReviewSubmitRequest,
} from "@/entities/statistics/model/statistics-interface";
import { useAlertStore } from "@/shared/lib";
export interface ReviewSubmitDialogProps { export interface ReviewSubmitDialogProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
context: ReviewerInfoRequest | null; context: ReviewerInfoRequest | null;
onSubmitted?: () => void | Promise<void>; onSubmitted?: () => void | Promise<void>;
/**
* true면 검토의견·처리내역 UI를 숨기고
* submit body에 statsCd / splmntOpnnCn / splmntRsltCn을 넣지 않음
*/
hideOpinionFields?: boolean;
} }
export function ReviewSubmitDialog({ export function ReviewSubmitDialog({
@@ -36,6 +41,7 @@ export function ReviewSubmitDialog({
onOpenChange, onOpenChange,
context, context,
onSubmitted, onSubmitted,
hideOpinionFields = false,
}: ReviewSubmitDialogProps) { }: ReviewSubmitDialogProps) {
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const [reviewerInfo, setReviewerInfo] = const [reviewerInfo, setReviewerInfo] =
@@ -98,14 +104,20 @@ export function ReviewSubmitDialog({
setIsSubmitting(true); setIsSubmitting(true);
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const submitParam: ReviewSubmitRequest = { const submitParam: ReviewSubmitRequest = hideOpinionFields
statsYr: context.statsYr, ? {
sewerBzmnCd: context.sewerBzmnCd, statsYr: context.statsYr,
statsCd: context.statsCd, sewerBzmnCd: [context.sewerBzmnCd],
mdfrId: userInfo.userUnqId, mdfrUuid: userInfo.userUnqId,
splmntOpnnCn: reviewerInfo?.splmntOpnnCn ?? "", }
wrterOpnnCn: reviewerInfo?.wrterOpnnCn ?? "", : {
}; statsYr: context.statsYr,
sewerBzmnCd: context.sewerBzmnCd,
statsCd: context.statsCd,
mdfrUuid: userInfo.userUnqId,
splmntOpnnCn: reviewerInfo?.splmntOpnnCn ?? "",
splmntRsltCn: reviewerInfo?.splmntRsltCn ?? "",
};
const response = await statisticsApi.submitReview(submitParam); const response = await statisticsApi.submitReview(submitParam);
if (response.status !== "success") { if (response.status !== "success") {
@@ -153,22 +165,26 @@ export function ReviewSubmitDialog({
<Label className="text-label">직책</Label> <Label className="text-label">직책</Label>
<Input value={reviewerInfo?.cps ?? "-"} readOnly /> <Input value={reviewerInfo?.cps ?? "-"} readOnly />
</div> </div>
<div className="col-span-2 flex flex-col gap-2"> {!hideOpinionFields && (
<Label className="text-label">검토의견</Label> <>
<Textarea <div className="col-span-2 flex flex-col gap-2">
value={reviewerInfo?.splmntOpnnCn ?? "-"} <Label className="text-label">검토의견</Label>
readOnly <Textarea
className="min-h-[10rem] p-2 resize-none !text-xl" value={reviewerInfo?.splmntOpnnCn ?? "-"}
/> readOnly
</div> className="min-h-[10rem] p-2 resize-none !text-xl"
<div className="col-span-2 flex flex-col gap-2"> />
<Label className="text-label">처리내역</Label> </div>
<Textarea <div className="col-span-2 flex flex-col gap-2">
value={reviewerInfo?.wrterOpnnCn ?? "-"} <Label className="text-label">처리내역</Label>
readOnly <Textarea
className="min-h-[10rem] p-2 resize-none !text-xl" value={reviewerInfo?.splmntRsltCn ?? "-"}
/> readOnly
</div> className="min-h-[10rem] p-2 resize-none !text-xl"
/>
</div>
</>
)}
</div> </div>
<DialogFooter> <DialogFooter>
<Button <Button
@@ -3,6 +3,10 @@ import { useMemo, useRef, useState } from "react";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
ReviewerInfoRequest,
ReviewOpinionSaveRequest,
} from "@/entities/statistics/model/statistics-interface";
import { import {
extractSewageExcretaRelatedReviewList, extractSewageExcretaRelatedReviewList,
extractSewageExcretaRelatedReviewStatsStatusInfo, extractSewageExcretaRelatedReviewStatsStatusInfo,
@@ -10,29 +14,25 @@ import {
type SewageExcretaRelatedReviewRequestParams, type SewageExcretaRelatedReviewRequestParams,
} from "@/entities/statistics/model/statistics-sewage-excreta-related-review-interface"; } from "@/entities/statistics/model/statistics-sewage-excreta-related-review-interface";
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewageExcretaRelatedInfoListRvw } from "@/components/util/column/review/SewageExcretaRelatedRvwColumn"; import { SewageExcretaRelatedInfoListRvw } from "@/components/util/column/review/SewageExcretaRelatedRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
const firstTabKey = SewageExcretaRelatedInfoListRvw[0]?.tabKey ?? "ST00120001"; const firstTabKey = SewageExcretaRelatedInfoListRvw[0]?.tabKey ?? "ST00120001";
@@ -105,7 +105,7 @@ const SewageExcretaRelatedRvw = () => {
}, [activeStatsStatusInfo.statsCd, lastSearchValues]); }, [activeStatsStatusInfo.statsCd, lastSearchValues]);
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? ""; const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? ""; const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => { const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (lastSearchValues == null) return null; if (lastSearchValues == null) return null;
@@ -130,11 +130,11 @@ const SewageExcretaRelatedRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = activeStatsStatusInfo; const statsStatusInfo = activeStatsStatusInfo;
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -289,7 +289,7 @@ const SewageExcretaRelatedRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: row?.splmntOpnnCn ?? "", splmntOpnnCn: row?.splmntOpnnCn ?? "",
wrterOpnnCn: row?.wrterOpnnCn ?? "", splmntRsltCn: row?.splmntRsltCn ?? "",
}); });
return next; return next;
}); });
@@ -312,7 +312,7 @@ const SewageExcretaRelatedRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
}); });
return next; return next;
}); });
@@ -3,6 +3,10 @@ import { useMemo, useRef, useState } from "react";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
ReviewerInfoRequest,
ReviewOpinionSaveRequest,
} from "@/entities/statistics/model/statistics-interface";
import { import {
extractSewageExcretaSlurgeReviewList, extractSewageExcretaSlurgeReviewList,
extractSewageExcretaSlurgeReviewStatsStatusInfo, extractSewageExcretaSlurgeReviewStatsStatusInfo,
@@ -10,29 +14,25 @@ import {
type SewageExcretaSlurgeReviewRequestParams, type SewageExcretaSlurgeReviewRequestParams,
} from "@/entities/statistics/model/statistics-sewage-excreta-slurge-review-interface"; } from "@/entities/statistics/model/statistics-sewage-excreta-slurge-review-interface";
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewageExcretaSludgeInfoListRvw } from "@/components/util/column/review/SewageExcretaSlurgeRvwColumn"; import { SewageExcretaSludgeInfoListRvw } from "@/components/util/column/review/SewageExcretaSlurgeRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
const firstTabKey = SewageExcretaSludgeInfoListRvw[0]?.tabKey ?? "ST00110001"; const firstTabKey = SewageExcretaSludgeInfoListRvw[0]?.tabKey ?? "ST00110001";
@@ -105,7 +105,7 @@ const SewageExcretaSlurgeRvw = () => {
}, [activeStatsStatusInfo.statsCd, lastSearchValues]); }, [activeStatsStatusInfo.statsCd, lastSearchValues]);
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? ""; const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? ""; const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => { const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (lastSearchValues == null) return null; if (lastSearchValues == null) return null;
@@ -130,11 +130,11 @@ const SewageExcretaSlurgeRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = activeStatsStatusInfo; const statsStatusInfo = activeStatsStatusInfo;
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -290,7 +290,7 @@ const SewageExcretaSlurgeRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: row?.splmntOpnnCn ?? "", splmntOpnnCn: row?.splmntOpnnCn ?? "",
wrterOpnnCn: row?.wrterOpnnCn ?? "", splmntRsltCn: row?.splmntRsltCn ?? "",
}); });
return next; return next;
}); });
@@ -313,7 +313,7 @@ const SewageExcretaSlurgeRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
}); });
return next; return next;
}); });
@@ -3,33 +3,33 @@ import { useMemo, useRef, useState } from "react";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-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 { import type {
ReviewerInfoRequest, ReviewerInfoRequest,
ReviewOpinionSaveRequest, ReviewOpinionSaveRequest,
} from "@/entities/statistics/model/statistics-interface"; } 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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn"; import { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
const firstTabKey = SewagePipeMaintainColumnInfoList[0]?.tabKey ?? "ST00030001"; const firstTabKey = SewagePipeMaintainColumnInfoList[0]?.tabKey ?? "ST00030001";
@@ -120,7 +120,7 @@ const SewagePipeMaintainRvw = () => {
}, [activeStatsStatusInfo.statsCd, lastSearchValues]); }, [activeStatsStatusInfo.statsCd, lastSearchValues]);
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? ""; const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? ""; const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => { const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (lastSearchValues == null) return null; if (lastSearchValues == null) return null;
@@ -145,11 +145,11 @@ const SewagePipeMaintainRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = activeStatsStatusInfo; const statsStatusInfo = activeStatsStatusInfo;
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -302,7 +302,7 @@ const SewagePipeMaintainRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: row?.splmntOpnnCn ?? "", splmntOpnnCn: row?.splmntOpnnCn ?? "",
wrterOpnnCn: row?.wrterOpnnCn ?? "", splmntRsltCn: row?.splmntRsltCn ?? "",
}); });
return next; return next;
}); });
@@ -325,7 +325,7 @@ const SewagePipeMaintainRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
}); });
return next; return next;
}); });
@@ -3,27 +3,28 @@ import { useMemo, useRef, useState } from "react";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
SewagePipeReviewRequestParams,
StatsStatusInfo,
} from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
import type { import type {
ReviewerInfoRequest, ReviewerInfoRequest,
ReviewOpinionSaveRequest, ReviewOpinionSaveRequest,
} from "@/entities/statistics/model/statistics-interface"; } 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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { import type {
ColumnConfig, ColumnConfig,
ColumnInfo, ColumnInfo,
@@ -31,10 +32,9 @@ import type {
ColumnLayoutItem, ColumnLayoutItem,
} from "@/components/type/DataGrid.type"; } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn"; import { getST00020003RvwConfig,SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
const firstTabKey = SewagePipeColumnInfoListRvw[0]?.tabKey ?? "ST00020001"; const firstTabKey = SewagePipeColumnInfoListRvw[0]?.tabKey ?? "ST00020001";
@@ -148,15 +148,23 @@ const SewagePipeRvw = () => {
[tabStatsStatusInfoMap, activeTab], [tabStatsStatusInfoMap, activeTab],
); );
const columnInfo = useMemo<ColumnInfoList[]>( const columnInfo = useMemo<ColumnInfoList[]>(
() => () => {
SewagePipeColumnInfoListRvw.map((tab) => ({ const statsYr = lastSearchValues?.year ?? new Date().getFullYear();
...tab, const st00020003Config = getST00020003RvwConfig(statsYr);
columns: (tab.columns ?? []).map((col) => cloneColumnConfig(col)), return SewagePipeColumnInfoListRvw.map((tab) => {
columnLayout: (tab.columnLayout ?? []).map((item) => const source =
cloneColumnLayoutItem(item), 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( const reviewCellStyleCallbackByTab = useMemo(
@@ -173,7 +181,7 @@ const SewagePipeRvw = () => {
}; };
}, [activeStatsStatusInfo.statsCd, lastSearchValues]); }, [activeStatsStatusInfo.statsCd, lastSearchValues]);
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? ""; const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? ""; const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => { const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (lastSearchValues == null) return null; if (lastSearchValues == null) return null;
@@ -197,11 +205,11 @@ const SewagePipeRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = activeStatsStatusInfo; const statsStatusInfo = activeStatsStatusInfo;
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -354,7 +362,7 @@ const SewagePipeRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: row?.splmntOpnnCn ?? "", splmntOpnnCn: row?.splmntOpnnCn ?? "",
wrterOpnnCn: row?.wrterOpnnCn ?? "", splmntRsltCn: row?.splmntRsltCn ?? "",
}); });
return next; return next;
}); });
@@ -377,7 +385,7 @@ const SewagePipeRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
}); });
return next; return next;
}); });
@@ -3,31 +3,31 @@ import { useMemo, useRef, useState } from "react";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-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 { SewerReservoirRequestParams } from "@/entities/statistics/model/sewage-reservoir-pump-review-interface";
import type { import type {
ReviewerInfoRequest, ReviewerInfoRequest,
ReviewOpinionSaveRequest, ReviewOpinionSaveRequest,
} from "@/entities/statistics/model/statistics-interface"; } 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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewageReservoirPumpInfoListRvw } from "@/components/util/column/review/SewageReservoirPumpRvwColumn"; import { SewageReservoirPumpInfoListRvw } from "@/components/util/column/review/SewageReservoirPumpRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
const firstTabKey = SewageReservoirPumpInfoListRvw[0]?.tabKey ?? "ST00040001"; const firstTabKey = SewageReservoirPumpInfoListRvw[0]?.tabKey ?? "ST00040001";
@@ -126,7 +126,7 @@ const SewageReservoirPumpRvw = () => {
}, [activeStatsCd, lastSearchValues]); }, [activeStatsCd, lastSearchValues]);
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? ""; const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? ""; const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => { const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (lastSearchValues == null) return null; if (lastSearchValues == null) return null;
@@ -151,11 +151,11 @@ const SewageReservoirPumpRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = activeStatsStatusInfo; const statsStatusInfo = activeStatsStatusInfo;
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -308,7 +308,7 @@ const SewageReservoirPumpRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: row?.splmntOpnnCn ?? "", splmntOpnnCn: row?.splmntOpnnCn ?? "",
wrterOpnnCn: row?.wrterOpnnCn ?? "", splmntRsltCn: row?.splmntRsltCn ?? "",
}); });
return next; return next;
}); });
@@ -331,7 +331,7 @@ const SewageReservoirPumpRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
}); });
return next; return next;
}); });
@@ -3,36 +3,36 @@ import { useMemo, useRef, useState } from "react";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-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 { import {
extractSewerageFinanceReviewList, extractSewerageFinanceReviewList,
extractSewerageFinanceReviewStatsStatusInfo, extractSewerageFinanceReviewStatsStatusInfo,
mapSewerageFinanceReviewRows, mapSewerageFinanceReviewRows,
type SewerageFinanceReviewRequestParams, type SewerageFinanceReviewRequestParams,
} from "@/entities/statistics/model/statistics-sewerage-finance-review-interface"; } 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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewerageFinanceInfoListRvw } from "@/components/util/column/review/SewerageFinanceRvwColumn"; import { SewerageFinanceInfoListRvw } from "@/components/util/column/review/SewerageFinanceRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
const firstTabKey = SewerageFinanceInfoListRvw[0]?.tabKey ?? "ST00090001"; const firstTabKey = SewerageFinanceInfoListRvw[0]?.tabKey ?? "ST00090001";
@@ -105,7 +105,7 @@ const SewerageFinanceRvw = () => {
}, [activeStatsStatusInfo.statsCd, lastSearchValues]); }, [activeStatsStatusInfo.statsCd, lastSearchValues]);
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? ""; const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? ""; const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => { const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (lastSearchValues == null) return null; if (lastSearchValues == null) return null;
@@ -130,11 +130,11 @@ const SewerageFinanceRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = activeStatsStatusInfo; const statsStatusInfo = activeStatsStatusInfo;
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -287,7 +287,7 @@ const SewerageFinanceRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: row?.splmntOpnnCn ?? "", splmntOpnnCn: row?.splmntOpnnCn ?? "",
wrterOpnnCn: row?.wrterOpnnCn ?? "", splmntRsltCn: row?.splmntRsltCn ?? "",
}); });
return next; return next;
}); });
@@ -310,7 +310,7 @@ const SewerageFinanceRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
}); });
return next; return next;
}); });
@@ -1,30 +1,31 @@
import { useState, useMemo } from "react"; import { useMemo,useState } 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 { useAlertStore, useUserStore } from "@/shared/lib"; 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 { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { toGridRows } from "@/components/util/grid/gridFunction"; import type {
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle"; 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 ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } 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.하수도 보급률 // 하수도 통계 검토 01.하수도 보급률
const SeweragePenetrationRateRvw = () => { const SeweragePenetrationRateRvw = () => {
@@ -62,8 +63,8 @@ const SeweragePenetrationRateRvw = () => {
); );
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const submit = statsStatusInfo.submitStatusCd != null ? String(statsStatusInfo.submitStatusCd) : ""; const submit = statsStatusInfo.lclgvSbmsnSttsCd != null ? String(statsStatusInfo.lclgvSbmsnSttsCd) : "";
const prvn = statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; const prvn = statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -139,7 +140,7 @@ const SeweragePenetrationRateRvw = () => {
response.data.statsStatusInfo.splmntOpnnCn ?? "", response.data.statsStatusInfo.splmntOpnnCn ?? "",
); );
setLatestProcessHistory( setLatestProcessHistory(
response.data.statsStatusInfo.wrterOpnnCn ?? "", response.data.statsStatusInfo.splmntRsltCn ?? "",
); );
setRowData(toGridRows(convertedData)); setRowData(toGridRows(convertedData));
} else { } else {
@@ -186,7 +187,7 @@ const SeweragePenetrationRateRvw = () => {
const row = detailRes.data?.[0]; const row = detailRes.data?.[0];
setLatestReviewOpinion(row?.splmntOpnnCn ?? ""); setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
setLatestProcessHistory(row?.wrterOpnnCn ?? ""); setLatestProcessHistory(row?.splmntRsltCn ?? "");
setReviewOpinionOpen(true); setReviewOpinionOpen(true);
} catch { } catch {
alert("검토의견 조회 중 오류가 발생했습니다."); alert("검토의견 조회 중 오류가 발생했습니다.");
@@ -204,10 +205,10 @@ const SeweragePenetrationRateRvw = () => {
setStatsStatusInfo((prev) => ({ setStatsStatusInfo((prev) => ({
...prev, ...prev,
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
})); }));
setLatestReviewOpinion(param.splmntOpnnCn); setLatestReviewOpinion(param.splmntOpnnCn);
setLatestProcessHistory(param.wrterOpnnCn); setLatestProcessHistory(param.splmntRsltCn);
} catch { } catch {
alert("저장 중 오류가 발생했습니다."); alert("저장 중 오류가 발생했습니다.");
} }
@@ -3,30 +3,31 @@ import { useMemo, useRef, useState } from "react";
import { useAlertStore, useUserStore } from "@/shared/lib"; import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-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 { import {
extractSewerageServiceLevelReviewList, extractSewerageServiceLevelReviewList,
extractSewerageServiceLevelReviewStatsStatusInfo, extractSewerageServiceLevelReviewStatsStatusInfo,
mapSewerageServiceLevelReviewRows, mapSewerageServiceLevelReviewRows,
type SewerageServiceLevelReviewRequestParams, type SewerageServiceLevelReviewRequestParams,
} from "@/entities/statistics/model/statistics-sewerage-service-level-review-interface"; } 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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { import type {
ColumnConfig, ColumnConfig,
ColumnInfo, ColumnInfo,
@@ -36,7 +37,6 @@ import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewerageServiceLevelInfoListRvw } from "@/components/util/column/review/SewerageServiceLevelRvwColumn"; import { SewerageServiceLevelInfoListRvw } from "@/components/util/column/review/SewerageServiceLevelRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
const firstTabKey = SewerageServiceLevelInfoListRvw[0]?.tabKey ?? "ST00130001"; const firstTabKey = SewerageServiceLevelInfoListRvw[0]?.tabKey ?? "ST00130001";
@@ -103,7 +103,7 @@ const SewerageServiceLevelRvw = () => {
}, [activeStatsStatusInfo.statsCd, lastSearchValues]); }, [activeStatsStatusInfo.statsCd, lastSearchValues]);
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? ""; const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? ""; const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => { const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (lastSearchValues == null) return null; if (lastSearchValues == null) return null;
@@ -128,11 +128,11 @@ const SewerageServiceLevelRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = activeStatsStatusInfo; const statsStatusInfo = activeStatsStatusInfo;
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -288,7 +288,7 @@ const SewerageServiceLevelRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: row?.splmntOpnnCn ?? "", splmntOpnnCn: row?.splmntOpnnCn ?? "",
wrterOpnnCn: row?.wrterOpnnCn ?? "", splmntRsltCn: row?.splmntRsltCn ?? "",
}); });
return next; return next;
}); });
@@ -311,7 +311,7 @@ const SewerageServiceLevelRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
}); });
return next; return next;
}); });
@@ -1,36 +1,42 @@
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import DataGrid from "@/components/module/DataGrid"; import { useAlertStore, useSearchConditionSidoList, useUserStore } from "@/shared/lib";
import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import SearchForm from "@/components/module/SearchForm"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import ApproveButton from "@/components/button/ApproveButton"; import type {
import { doList } from "@/components/util/column/input/DoList"; 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 { import {
Button, Button,
DataTable,
Dialog, Dialog,
DialogContent, DialogContent,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
LoadingWithAnimation, LoadingWithAnimation} from "@common/components";
DataTable
} 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 { 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]; type ReviewHistoryRow = ReviewHistoryResponse["data"][number];
const checkPrvnStatusCd = (code: string) => { const checkPrvnStatusCd = (code: string) => {
@@ -54,7 +60,9 @@ const mapSewerageListToGridRows = (rawList: StatsInputStatusReviewItem[]) =>
// 하수도 통계 검토 00. 통계 입력 현황 // 하수도 통계 검토 00. 통계 입력 현황
const StatsInputStatusRvw = () => { const StatsInputStatusRvw = () => {
const { sidoList } = useSearchConditionSidoList();
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const userInfo = useUserStore((state) => state.userInfo);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]); const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [historyRowData, setHistoryRowData] = useState<ReviewHistoryRow[]>([]); const [historyRowData, setHistoryRowData] = useState<ReviewHistoryRow[]>([]);
@@ -63,6 +71,7 @@ const StatsInputStatusRvw = () => {
const [searchedYear, setSearchedYear] = useState<string>(""); const [searchedYear, setSearchedYear] = useState<string>("");
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null); const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
const [hasCompletedSearch, setHasCompletedSearch] = useState(false); const [hasCompletedSearch, setHasCompletedSearch] = useState(false);
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
const [reviewHistoryOpen, setReviewHistoryOpen] = useState<boolean>(false); const [reviewHistoryOpen, setReviewHistoryOpen] = useState<boolean>(false);
const [historySearchInfo, setHistorySearchInfo] = useState<{ const [historySearchInfo, setHistorySearchInfo] = useState<{
ctpvNm: string; ctpvNm: string;
@@ -112,6 +121,7 @@ const StatsInputStatusRvw = () => {
setLastSearchedValues(null); setLastSearchedValues(null);
setHasCompletedSearch(false); setHasCompletedSearch(false);
setIsApproveLoading(false); setIsApproveLoading(false);
setSubmitDialogOpen(false);
}; };
const handleSearch = async (values: SearchFormValues) => { const handleSearch = async (values: SearchFormValues) => {
@@ -174,8 +184,8 @@ const StatsInputStatusRvw = () => {
setIsApproveLoading(true); setIsApproveLoading(true);
try { try {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const mdfrId = (userInfo.userUnqId ?? "").trim(); const mdfrUuid = (userInfo.userUnqId ?? "").trim();
if (!mdfrId) { if (!mdfrUuid) {
alert("수정자 ID를 확인할 수 없습니다."); alert("수정자 ID를 확인할 수 없습니다.");
return; return;
} }
@@ -187,7 +197,7 @@ const StatsInputStatusRvw = () => {
.map((row) => { .map((row) => {
const statsCd = String(row.statsCd ?? "").trim(); const statsCd = String(row.statsCd ?? "").trim();
const sewerBzmnCd = String(row.sewerBzmnCd ?? sewerBzmnCdDefault).trim(); const sewerBzmnCd = String(row.sewerBzmnCd ?? sewerBzmnCdDefault).trim();
return { statsYr, sewerBzmnCd, statsCd, mdfrId }; return { statsYr, sewerBzmnCd, statsCd, mdfrUuid };
}) })
.filter((item) => item.statsCd.length > 0); .filter((item) => item.statsCd.length > 0);
@@ -223,9 +233,7 @@ const StatsInputStatusRvw = () => {
} }
}; };
const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || "") const gugunOkOrNotDoList = isGugunOkForToolbar(sidoList, lastSearchedValues);
? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== "ALL")
: true;
const allSubmitStatusApproved = useMemo( const allSubmitStatusApproved = useMemo(
() => () =>
@@ -242,13 +250,74 @@ const StatsInputStatusRvw = () => {
[rowData], [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 && hasCompletedSearch &&
rowData.length > 0 && rowData.length > 0 &&
gugunOkOrNotDoList && gugunOkOrNotDoList &&
allSubmitStatusApproved && allSubmitStatusApproved &&
!allPrvnStatusApproved; !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( const openReviewHistoryForRow = useCallback(
async (row: Record<string, string | number>) => { async (row: Record<string, string | number>) => {
if (!searchedYear) { if (!searchedYear) {
@@ -297,7 +366,10 @@ const StatsInputStatusRvw = () => {
{showActionToolbarCard && ( {showActionToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]"> <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>
</div> </div>
)} )}
@@ -357,6 +429,13 @@ const StatsInputStatusRvw = () => {
} }
}} }}
/> />
<ReviewSubmitDialog
open={submitDialogOpen}
onOpenChange={setSubmitDialogOpen}
context={submitDialogContext}
hideOpinionFields
onSubmitted={() => void refreshListAfterSubmit()}
/>
<Dialog open={reviewHistoryOpen} onOpenChange={setReviewHistoryOpen}> <Dialog open={reviewHistoryOpen} onOpenChange={setReviewHistoryOpen}>
<DialogContent <DialogContent
className="flex max-h-[85vh] max-w-[48rem] flex-col gap-4" 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 { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-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 { import {
extractWaterReuseReviewList, extractWaterReuseReviewList,
extractWaterReuseReviewStatsStatusInfo, extractWaterReuseReviewStatsStatusInfo,
mapWaterReuseReviewRows, mapWaterReuseReviewRows,
type WaterReuseReviewRequestParams, type WaterReuseReviewRequestParams,
} from "@/entities/statistics/model/statistics-water-reuse-review-interface"; } 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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SubmitButton from "@/components/button/SumitButton";
import ReturnButton from "@/components/button/ReturnButton"; import ReturnButton from "@/components/button/ReturnButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import SubmitButton from "@/components/button/SumitButton";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm"; import SearchForm from "@/components/module/SearchForm";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog"; import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { import {
ReviewOpinionDialog, ReviewOpinionDialog,
type ReviewOpinionDialogContext, type ReviewOpinionDialogContext,
} from "@/components/statistics/review/ReviewOpinionDialog"; } from "@/components/statistics/review/ReviewOpinionDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { WaterReuseInfoListRvw } from "@/components/util/column/review/WaterReuseRvwColumn"; import { WaterReuseInfoListRvw } from "@/components/util/column/review/WaterReuseRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
const firstTabKey = WaterReuseInfoListRvw[0]?.tabKey ?? "ST00100001"; const firstTabKey = WaterReuseInfoListRvw[0]?.tabKey ?? "ST00100001";
@@ -108,7 +108,7 @@ const WaterReuseRvw = () => {
}, [activeStatsStatusInfo.statsCd, lastSearchValues]); }, [activeStatsStatusInfo.statsCd, lastSearchValues]);
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? ""; const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? ""; const latestProcessHistory = activeStatsStatusInfo.splmntRsltCn ?? "";
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => { const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (lastSearchValues == null) return null; if (lastSearchValues == null) return null;
@@ -133,11 +133,11 @@ const WaterReuseRvw = () => {
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = activeStatsStatusInfo; const statsStatusInfo = activeStatsStatusInfo;
const submit = const submit =
statsStatusInfo.submitStatusCd != null statsStatusInfo.lclgvSbmsnSttsCd != null
? String(statsStatusInfo.submitStatusCd) ? String(statsStatusInfo.lclgvSbmsnSttsCd)
: ""; : "";
const prvn = const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : ""; statsStatusInfo.mctpvPicSbmsnSttsCd != null ? String(statsStatusInfo.mctpvPicSbmsnSttsCd) : "";
const submitIs3 = submit === "3"; const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1"); const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return { return {
@@ -291,7 +291,7 @@ const WaterReuseRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: row?.splmntOpnnCn ?? "", splmntOpnnCn: row?.splmntOpnnCn ?? "",
wrterOpnnCn: row?.wrterOpnnCn ?? "", splmntRsltCn: row?.splmntRsltCn ?? "",
}); });
return next; return next;
}); });
@@ -314,7 +314,7 @@ const WaterReuseRvw = () => {
next.set(activeTab, { next.set(activeTab, {
...(next.get(activeTab) ?? {}), ...(next.get(activeTab) ?? {}),
splmntOpnnCn: param.splmntOpnnCn, splmntOpnnCn: param.splmntOpnnCn,
wrterOpnnCn: param.wrterOpnnCn, splmntRsltCn: param.splmntRsltCn,
}); });
return next; return next;
}); });
@@ -1,24 +1,23 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib"; 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 및 인터페이스 명세 호출 // 집계표(TOTAL) API 및 인터페이스 명세 호출
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service"; import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
import type { import type {
ExcretaTreatmentMaintainTotalRequest ExcretaTreatmentMaintainTotalRequest
} from "@/entities/statistics/model/statistics-total/statistics-excreta-treatment-total-interface"; } from "@/entities/statistics/model/statistics-total/statistics-excreta-treatment-total-interface";
import { LoadingWithAnimation } from "@common/components";
import CellColorInfo from "./CellColorInfo"; 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 ExcretaTreatmentFacilityTot = () => {
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
@@ -84,7 +83,7 @@ const ExcretaTreatmentFacilityTot = () => {
return ( return (
<div className="relative"> <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 && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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} // 필수 프로퍼티 에러 해결 setRowData={setRowData} // 필수 프로퍼티 에러 해결
isExcelImport={false} isExcelImport={false}
isExcelExport={true} isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false} showHeaderCheckbox={false}
fixedColumns={config.fixedColumns} fixedColumns={config.fixedColumns}
enableSort={false} enableSort={false}
@@ -1,26 +1,25 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib"; 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 명세 임포트 // 통계 API 서비스 및 통합 DTO 명세 임포트
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service"; import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
import type { import type {
PrivateSewageFacilitySummaryRequest PrivateSewageFacilitySummaryRequest
} from "@/entities/statistics/model/statistics-total/statistics-private-sewage-total-interface"; } 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 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 = [ const PrivateSewageColumnListTot = [
PrivateSewageTab1ColumnConfig, PrivateSewageTab1ColumnConfig,
PrivateSewageTab2ColumnConfig, PrivateSewageTab2ColumnConfig,
@@ -132,7 +131,7 @@ const PrivateSewageFacilityTot = () => {
return ( return (
<div className="relative"> <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% 싱크 맞춘 중앙 정렬 소형 팝업 로딩 바 고정 */} {/* 하수관로 규칙과 100% 싱크 맞춘 중앙 정렬 소형 팝업 로딩 바 고정 */}
{isLoading && ( {isLoading && (
@@ -184,6 +183,7 @@ const PrivateSewageFacilityTot = () => {
}} }}
isExcelImport={false} isExcelImport={false}
isExcelExport={true} isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false} showHeaderCheckbox={false}
fixedColumns={info.fixedColumns} fixedColumns={info.fixedColumns}
enableSort={false} enableSort={false}
@@ -1,15 +1,16 @@
import { useState } from "react"; 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 type { SearchFormValues } from "@/components/type/SearchForm.type";
import { PublicSewageFacilityColumn } from "@/components/util/column/total/PublicSewageFacilityColumn"; 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 { 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 PublicSewageFacilityTot = () => {
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]); const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
@@ -52,7 +53,7 @@ const PublicSewageFacilityTot = () => {
return ( 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 && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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]"> <div className="relative size-[5rem]">
@@ -65,6 +66,7 @@ const PublicSewageFacilityTot = () => {
columnInfo={PublicSewageFacilityColumn} columnInfo={PublicSewageFacilityColumn}
rowStartNum={2} rowStartNum={2}
isExcelImport={false} isExcelImport={false}
excelExportApplyStyles
fixedColumns={2} fixedColumns={2}
rowData={rowData} rowData={rowData}
setRowData={setRowData} setRowData={setRowData}
@@ -1,24 +1,23 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib"; 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 및 인터페이스 명세 호출 // 집계표(TOTAL) API 및 인터페이스 명세 호출
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service"; import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
import type { import type {
MediationPumpMaintainTotalRequest MediationPumpMaintainTotalRequest
} from "@/entities/statistics/model/statistics-total/statistics-mediation-pump-total-interface"; } from "@/entities/statistics/model/statistics-total/statistics-mediation-pump-total-interface";
import { LoadingWithAnimation } from "@common/components";
import CellColorInfo from "./CellColorInfo"; 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 PumpingStationTot = () => {
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
@@ -84,7 +83,7 @@ const PumpingStationTot = () => {
return ( return (
<div className="relative"> <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 && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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} setRowData={setRowData}
isExcelImport={false} isExcelImport={false}
isExcelExport={true} isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false} showHeaderCheckbox={false}
fixedColumns={config.fixedColumns} fixedColumns={config.fixedColumns}
enableSort={false} enableSort={false}
@@ -1,25 +1,26 @@
import { useRef, useState } from "react"; 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 { useAlertStore } from "@/shared/lib";
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 { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service"; import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
import type { import type {
SewerageIndustry,
PublicSewageStaffTab1Response,
PublicHumanWasteStaffTab2Response,
HumanWasteCollectionTab3Response, HumanWasteCollectionTab3Response,
IndividualSewageTab4Response IndividualSewageTab4Response,
} from "@/entities/statistics/model/statistics-total/statistics-sewerage-industry-interface"; 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 SewageExcretaIndustryColumnListTot = [PublicSewageStaffTab1ColumnConfig,PublicHumanWasteStaffTab2ColumnConfig,HumanWasteCollectionTab3ColumnConfig,IndividualSewageTab4ColumnConfig].filter(Boolean);
const firstTabKey = SewageExcretaIndustryColumnListTot[0]?.tabKey ?? "ST00120001"; const firstTabKey = SewageExcretaIndustryColumnListTot[0]?.tabKey ?? "ST00120001";
@@ -102,7 +103,7 @@ const SewageExcretaRelatedTot = () => {
return ( return (
<div className="relative"> <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 && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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} isExcelImport={false}
isExcelExport={true} isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false} showHeaderCheckbox={false}
fixedColumns={info.fixedColumns} fixedColumns={info.fixedColumns}
enableSort={false} enableSort={false}
@@ -1,30 +1,28 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib"; 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 인스턴스 및 인터페이스 임포트 (타입 안전성 확보) // API 인스턴스 및 인터페이스 임포트 (타입 안전성 확보)
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service"; import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
import type { import type {
SludgeSummaryRequest,
SludgeProcessingTab1Response,
DigestionTankOperTab2Response, DigestionTankOperTab2Response,
HumanWasteSludgeTab4Response,
SewageSludgeOperTab3Response, SewageSludgeOperTab3Response,
HumanWasteSludgeTab4Response SludgeProcessingTab1Response,
} from "@/entities/statistics/model/statistics-total/statistics-sludge-total-interface"; 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 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 = [ const SludgeColumnListTot = [
SludgeProcessingTab1ColumnConfig, SludgeProcessingTab1ColumnConfig,
@@ -146,7 +144,7 @@ const loadGridData = async (tabKey: string, values: SearchFormValues): Promise<b
return ( return (
<div className="relative"> <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 && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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} isExcelImport={false}
isExcelExport={true} isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false} showHeaderCheckbox={false}
fixedColumns={info.fixedColumns} fixedColumns={info.fixedColumns}
enableSort={false} enableSort={false}
@@ -2,15 +2,15 @@ import { useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib"; import { useAlertStore } from "@/shared/lib";
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service"; 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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import SearchForm from "@/components/module/SearchForm";
import DataGrid from "@/components/module/DataGrid"; import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import type { ColumnInfo } from "@/components/type/DataGrid.type"; import type { ColumnInfo } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewagePipeMaintainColumnListTot } from "@/components/util/column/total/sewagePipeMaintain/sewagePipeMaintainColumn"; import { SewagePipeMaintainColumnListTot } from "@/components/util/column/total/sewagePipeMaintain/sewagePipeMaintainColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; 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"; const firstTabKey = SewagePipeMaintainColumnListTot[0]?.tabKey ?? "ST00030001";
@@ -133,7 +133,7 @@ const SewagePipeMaintainTot = () => {
return ( return (
<div className="relative"> <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 && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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]"> <div className="relative size-[5rem]">
@@ -186,6 +186,7 @@ const SewagePipeMaintainTot = () => {
}} }}
isExcelImport={false} isExcelImport={false}
isExcelExport isExcelExport
excelExportApplyStyles
showHeaderCheckbox={false} showHeaderCheckbox={false}
fixedColumns={info.fixedColumns} fixedColumns={info.fixedColumns}
enableSort={false} enableSort={false}
@@ -2,16 +2,17 @@ import { useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib"; import { useAlertStore } from "@/shared/lib";
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service"; 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 { 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 DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import type { ColumnInfo } from "@/components/type/DataGrid.type"; import type { ColumnInfo } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.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 { 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"; const firstTabKey = SewagePipeColumnInfoListTot[0]?.tabKey ?? "ST00020001";
@@ -178,7 +179,7 @@ const SewagePipeTot = () => {
return ( return (
<div className="relative"> <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 && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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]"> <div className="relative size-[5rem]">
@@ -204,11 +205,10 @@ const SewagePipeTot = () => {
{columnInfo.map((info) => { {columnInfo.map((info) => {
const tabKey = info.tabKey; const tabKey = info.tabKey;
// ★ [동적 처리] ST00020003인 경우 함수를 통해 컬럼 정보를 실시간으로 생성 // ★ [동적 처리] ST00020003은 함수를 통해 컬럼 정보를 실시간으로 생성
const currentYear = lastSearchedValuesRef.current?.year || new Date().getFullYear(); const currentYear = lastSearchedValuesRef.current?.year || new Date().getFullYear();
const finalTabConfig = tabKey === "ST00020003" const finalTabConfig =
? getST00020003Config(currentYear) tabKey === "ST00020003" ? getST00020003Config(currentYear) : info;
: info;
if (!finalTabConfig.columns || !finalTabConfig.columnLayout) { if (!finalTabConfig.columns || !finalTabConfig.columnLayout) {
return null; return null;
@@ -239,6 +239,7 @@ const SewagePipeTot = () => {
}} }}
isExcelImport={false} isExcelImport={false}
isExcelExport isExcelExport
excelExportApplyStyles
showHeaderCheckbox={false} showHeaderCheckbox={false}
fixedColumns={finalTabConfig.fixedColumns} fixedColumns={finalTabConfig.fixedColumns}
enableSort={false} enableSort={false}
@@ -1,25 +1,24 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib"; 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 및 인터페이스 명세 호출 유지 // 수정 적용한 API 및 인터페이스 명세 호출 유지
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service"; import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
import type { import type {
SewerReservoirMaintainTotalRequest SewerReservoirMaintainTotalRequest
} from "@/entities/statistics/model/statistics-total/statistics-sewage-reservoir-total-interface"; } 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 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 = [ const SewerReservoirColumnListTot = [
ReservoirStatusColumnConfig, ReservoirStatusColumnConfig,
PumpStationStatusColumnConfig PumpStationStatusColumnConfig
@@ -118,7 +117,7 @@ const SewageReservoirPumpTot = () => {
return ( return (
<div className="relative"> <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 && ( {isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> <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} isExcelImport={false}
isExcelExport={true} isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false} showHeaderCheckbox={false}
fixedColumns={info.fixedColumns} fixedColumns={info.fixedColumns}
enableSort={false} enableSort={false}
@@ -199,6 +199,7 @@ const SewerageAggregateSummary = () => {
<SearchForm <SearchForm
isSummary={true} isSummary={true}
sidoSelectText="전체" sidoSelectText="전체"
defaultRegionAll
searchButtonText="내려받기" searchButtonText="내려받기"
onSubmit={(values) => void handleDownload(values)} onSubmit={(values) => void handleDownload(values)}
isReset={false} isReset={false}

Some files were not shown because too many files have changed in this diff Show More