Merge branch 'iipbs/st/dev'

This commit is contained in:
2026-05-11 15:59:24 +09:00
38 changed files with 2536 additions and 321 deletions
+13
View File
@@ -29,6 +29,13 @@
.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];
} }
/* 검증결과 내역: 처리여부(prcsYn 0·4 미처리 등) */
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-vrf-prcs-bad {
@apply !font-semibold !text-[#dc2626];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-vrf-prcs-ok {
@apply !font-semibold !text-[#3372cf];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-total { .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-total {
@apply !bg-[#e3eaf2]; @apply !bg-[#e3eaf2];
} }
@@ -95,6 +102,12 @@
.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-[#545556] !text-[#fff]; @apply !bg-[#545556] !text-[#fff];
} }
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-vrf-prcs-bad {
@apply !text-[#f87171];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-vrf-prcs-ok {
@apply !text-[#3372cf];
}
} }
[data-theme="red"] { [data-theme="red"] {
@@ -25,6 +25,7 @@ import {
import { import {
defaultSearchFormProps, defaultSearchFormProps,
type SearchFormClsfCd,
type SearchFormProps, type SearchFormProps,
type SearchFormValues, type SearchFormValues,
} from "../type/SearchForm.type"; } from "../type/SearchForm.type";
@@ -81,6 +82,12 @@ const SearchForm = (props: SearchFormProps = {}) => {
}, },
); );
const initialClsfCd: SearchFormClsfCd = (() => {
const d = props.defaultValues?.clsfCd;
if (d === "ALL" || d === "INPT" || d === "VRFC") return d;
return "ALL";
})();
const [values, setValues] = useState<SearchFormValues>({ const [values, setValues] = useState<SearchFormValues>({
sido: props.defaultValues?.sido || "", sido: props.defaultValues?.sido || "",
gugun: props.defaultValues?.gugun || "", gugun: props.defaultValues?.gugun || "",
@@ -90,6 +97,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
bsnmCd: props.defaultValues?.bsnmCd || "", bsnmCd: props.defaultValues?.bsnmCd || "",
isDetail: false, isDetail: false,
isSummary: props.defaultValues?.isSummary || "", isSummary: props.defaultValues?.isSummary || "",
clsfCd: initialClsfCd,
}); });
const sggKey: [string, string, string, string] | null = const sggKey: [string, string, string, string] | null =
@@ -134,6 +142,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
bsnmCd: "", bsnmCd: "",
isDetail: false, isDetail: false,
isSummary: "", isSummary: "",
clsfCd: "ALL",
}; };
setValues(resetValues); setValues(resetValues);
props.onReset?.(); props.onReset?.();
@@ -250,6 +259,29 @@ const SearchForm = (props: SearchFormProps = {}) => {
</Select> </Select>
</div> </div>
)} )}
{mergedProps.clsf && (
<div className="flex items-center gap-2">
<Label className="text-label">분류</Label>
<Select
value={values.clsfCd ?? "ALL"}
onValueChange={(value) =>
setValues({
...values,
clsfCd: value as SearchFormClsfCd,
})
}
>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체</SelectItem>
<SelectItem value="INPT">입력</SelectItem>
<SelectItem value="VRFC">검증</SelectItem>
</SelectContent>
</Select>
</div>
)}
{mergedProps.fctName && ( {mergedProps.fctName && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Label <Label
@@ -14,6 +14,8 @@ const SplitDataGrid = ({
setRightRowData, setRightRowData,
leftOnRowClick, leftOnRowClick,
leftCellStyleCallback, leftCellStyleCallback,
rightOnItemChecked,
rightOnItemAllChecked,
enableSort = false, enableSort = false,
}: SplitDataGridProps) => { }: SplitDataGridProps) => {
return ( return (
@@ -35,6 +37,8 @@ const SplitDataGrid = ({
enableSort={enableSort} enableSort={enableSort}
rowData={rightRowData} rowData={rightRowData}
setRowData={setRightRowData} setRowData={setRightRowData}
onItemChecked={rightOnItemChecked}
onItemAllChecked={rightOnItemAllChecked}
/> />
</div> </div>
); );
@@ -1,6 +1,9 @@
"use client"; "use client";
import { type Dispatch, type SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; import { type Dispatch, type SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
import { import {
Button, Button,
Dialog, Dialog,
@@ -11,20 +14,29 @@ import {
DialogTitle, DialogTitle,
LoadingWithAnimation, LoadingWithAnimation,
} from "@common/components"; } from "@common/components";
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 { ColumnInfo } from "@/components/type/DataGrid.type"; import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
type VerificationResultGridRow = Record<string, string | number>; type VerificationResultGridRow = Record<string, string | number>;
const PRCS_YN_LABEL: Record<string, string> = { function prcsYnCodeToNumber(raw: string | number | undefined): number | undefined {
"0": "미통과", if (raw === undefined || raw === null) return undefined;
"1": "통과", if (typeof raw === "number" && Number.isFinite(raw)) return raw;
"2": "예외처리", const s = String(raw).trim();
"3": "반려", if (s === "") return undefined;
}; const n = Number.parseInt(s, 10);
return Number.isFinite(n) ? n : undefined;
}
/** prcsYn 0·4 → 빨간색, 그 외 → 초록색 (처리여부 컬럼) */
function verificationPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined {
if (fieldName !== "prcsYnNm") return undefined;
const code = prcsYnCodeToNumber(row.prcsYn);
if (code === 0 || code === 4) return "rg-cell-vrf-prcs-bad";
return "rg-cell-vrf-prcs-ok";
}
const verificationResultColumnInfo: ColumnInfo = { const verificationResultColumnInfo: ColumnInfo = {
columns: [ columns: [
@@ -34,12 +46,13 @@ const verificationResultColumnInfo: ColumnInfo = {
{ 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: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false }, { name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false },
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "처리여부" }, editable: false }, { name: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false },
{ name: "pbcpAprvYn", fieldName: "pbcpAprvYn", header: { text: "공단승인여부" }, editable: false }, { name: "pbcpAprvYn", fieldName: "pbcpAprvYn", 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 },
], ],
columnLayout: ["ctpvNm", "sggNm", "vrfcTrgtNm", "rsnExpln", "nowVlCn", "compVlCn", "prcsYn", "pbcpAprvYn", "regDt", "mdfcnDt"], columnLayout: ["ctpvNm", "sggNm", "vrfcTrgtNm", "rsnExpln", "nowVlCn", "compVlCn", "prcsYnNm", "pbcpAprvYn", "regDt", "mdfcnDt", "prcsYn"],
}; };
export interface VerificationResultHistoryDialogProps { export interface VerificationResultHistoryDialogProps {
@@ -190,7 +203,8 @@ export function VerificationResultHistoryDialog({
rsnExpln: item.rsnExpln, rsnExpln: item.rsnExpln,
nowVlCn: item.nowVlCn ?? "-", nowVlCn: item.nowVlCn ?? "-",
compVlCn: item.compVlCn ?? "-", compVlCn: item.compVlCn ?? "-",
prcsYn: PRCS_YN_LABEL[item.prcsYn] ?? item.prcsYn, prcsYnNm: item.prcsYnNm ?? "-",
prcsYn: item.prcsYn ?? "",
pbcpAprvYn: item.pbcpAprvYn, pbcpAprvYn: item.pbcpAprvYn,
regDt: (item.regDt ?? "").slice(0, 16), regDt: (item.regDt ?? "").slice(0, 16),
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16), mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
@@ -224,6 +238,7 @@ export function VerificationResultHistoryDialog({
isExcelExport={false} isExcelExport={false}
showHeaderCheckbox={false} showHeaderCheckbox={false}
enableSort={false} enableSort={false}
cellStyleCallback={verificationPrcsYnNmCellStyle}
/> />
<VerificationResultHistoryPagination <VerificationResultHistoryPagination
pageCount={Math.max(pageCount, 1)} pageCount={Math.max(pageCount, 1)}
@@ -215,7 +215,7 @@ const VerificationSelectionReport = ({
<div className="w-1/5 bg-container p-4 !pb-2 flex border-r border-border"> <div className="w-1/5 bg-container p-4 !pb-2 flex border-r border-border">
<div className="card card-border flex-1 bg-background !px-0"> <div className="card card-border flex-1 bg-background !px-0">
<div className="border-b border-border flex justify-between"> <div className="border-b border-border flex justify-between">
<h2 className="text-[2.4rem] font-semibold text-title p-4 ">검증 미통과 내역</h2> <h2 className="text-[2.4rem] font-semibold text-title p-4 ">이상치 선별 보고서</h2>
<span <span
role="button" role="button"
tabIndex={0} tabIndex={0}
@@ -0,0 +1,224 @@
"use client";
import { useEffect, useState } from "react";
import { useAlertStore } from "@/shared/lib";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Input,
Label,
} from "@common/components";
export interface MainFacilityMappingSaveDialogValues {
prxyBzentyNmCn: string;
/** 위탁비용(백만원/년) */
cnsgnCstVl: number;
wholNope: number;
/** YYYYMMDD~YYYYMMDD */
prxyCtrtCn: string;
}
export interface MainFacilityMappingSaveDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
initialPrxyBzentyNmCn: string;
/** 위탁비용(백만원/년) 초기값 */
initialCnsgnCstVl: number;
initialWholNope: number;
/** 위탁계약기간 초기값 (YYYYMMDD~YYYYMMDD) */
initialPrxyCtrtCn: string;
onConfirm?: (values: MainFacilityMappingSaveDialogValues) => void | Promise<void>;
}
function isValidYmd(ymd: string): boolean {
if (!/^\d{8}$/.test(ymd)) return false;
const y = Number(ymd.slice(0, 4));
const m = Number(ymd.slice(4, 6));
const d = Number(ymd.slice(6, 8));
const dt = new Date(y, m - 1, d);
return dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d;
}
function validatePrxyCtrtCn(value: string): string | null {
const t = value.trim();
if (!t) return "위탁계약기간을 입력해 주세요.";
if (!/^\d{8}~\d{8}$/.test(t)) {
return "위탁계약기간은 YYYYMMDD~YYYYMMDD 형식으로 입력해 주세요. (예: 20240101~20281231)";
}
const start = t.slice(0, 8);
const end = t.slice(9, 17);
if (!isValidYmd(start) || !isValidYmd(end)) {
return "시작일 또는 종료일이 유효한 날짜가 아닙니다.";
}
if (start > end) {
return "시작일이 종료일보다 늦을 수 없습니다.";
}
return null;
}
export function MainFacilityMappingSaveDialog({
open,
onOpenChange,
initialPrxyBzentyNmCn,
initialCnsgnCstVl,
initialWholNope,
initialPrxyCtrtCn,
onConfirm,
}: MainFacilityMappingSaveDialogProps) {
const { setMessage: alert } = useAlertStore((state) => state);
const [prxyBzentyNmCn, setPrxyBzentyNmCn] = useState("");
const [cnsgnCstInput, setCnsgnCstInput] = useState("");
const [wholNopeInput, setWholNopeInput] = useState("");
const [prxyCtrtCn, setPrxyCtrtCn] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
if (open) {
setPrxyBzentyNmCn(initialPrxyBzentyNmCn ?? "");
const initCost = Number(initialCnsgnCstVl);
setCnsgnCstInput(
initialCnsgnCstVl === null ||
initialCnsgnCstVl === undefined ||
Number.isNaN(initCost)
? ""
: String(initCost),
);
setWholNopeInput(
initialWholNope === null || initialWholNope === undefined || Number.isNaN(Number(initialWholNope))
? ""
: String(initialWholNope),
);
setPrxyCtrtCn((initialPrxyCtrtCn ?? "").trim());
}
}, [open, initialPrxyBzentyNmCn, initialCnsgnCstVl, initialWholNope, initialPrxyCtrtCn]);
const handleDialogOpenChange = (nextOpen: boolean) => {
if (!nextOpen && !isSubmitting) {
setPrxyBzentyNmCn("");
setCnsgnCstInput("");
setWholNopeInput("");
setPrxyCtrtCn("");
}
onOpenChange(nextOpen);
};
const handleConfirm = async () => {
const trimmedCost = cnsgnCstInput.trim();
const cnsgnCstVl = trimmedCost === "" ? 0 : Number.parseFloat(trimmedCost.replace(/,/g, ""));
if (!Number.isFinite(cnsgnCstVl) || cnsgnCstVl < 0) {
alert("위탁비용은 0 이상의 숫자로 입력해 주세요.");
return;
}
const trimmedWhol = wholNopeInput.trim();
const wholNope = trimmedWhol === "" ? 0 : Number.parseInt(trimmedWhol, 10);
if (!Number.isFinite(wholNope) || wholNope < 0) {
alert("전체 인원은 0 이상의 정수로 입력해 주세요.");
return;
}
const prxyCtrtErr = validatePrxyCtrtCn(prxyCtrtCn);
if (prxyCtrtErr) {
alert(prxyCtrtErr);
return;
}
try {
setIsSubmitting(true);
await onConfirm?.({
prxyBzentyNmCn: prxyBzentyNmCn.trim(),
cnsgnCstVl,
wholNope,
prxyCtrtCn: prxyCtrtCn.trim(),
});
} finally {
setIsSubmitting(false);
}
};
return (
<Dialog open={open} onOpenChange={handleDialogOpenChange}>
<DialogContent className="max-w-[48rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>저장 정보 입력</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4 py-2">
<div className="flex flex-col gap-2">
<Label className="text-label">위탁업체명</Label>
<Input
value={prxyBzentyNmCn}
onChange={(e) => setPrxyBzentyNmCn(e.target.value)}
className="!text-[1.3rem]"
placeholder="위탁사업자명을 입력하세요."
disabled={isSubmitting}
/>
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">위탁비용(백만원/년)</Label>
<Input
type="text"
inputMode="decimal"
value={cnsgnCstInput}
onChange={(e) => {
const v = e.target.value.replace(/[^\d.]/g, "");
const firstDot = v.indexOf(".");
const next =
firstDot === -1
? v
: v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
setCnsgnCstInput(next);
}}
className="!text-[1.3rem]"
placeholder="0"
disabled={isSubmitting}
/>
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">전체 인원</Label>
<Input
type="text"
inputMode="numeric"
value={wholNopeInput}
onChange={(e) => {
const v = e.target.value.replace(/\D/g, "");
setWholNopeInput(v);
}}
className="!text-[1.3rem]"
placeholder="0"
disabled={isSubmitting}
/>
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">위탁계약기간</Label>
<Input
value={prxyCtrtCn}
onChange={(e) => setPrxyCtrtCn(e.target.value)}
className="!text-[1.3rem]"
placeholder="YYYYMMDD~YYYYMMDD (예: 20240101~20281231)"
autoComplete="off"
disabled={isSubmitting}
/>
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button type="button" color="gray" disabled={isSubmitting}>
취소
</Button>
</DialogClose>
<Button
type="button"
color="primary"
disabled={isSubmitting}
onClick={() => void handleConfirm()}
>
저장
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -2,7 +2,7 @@ import { useRef, useState } from 'react';
import { useConfirm } from '@frontend-opensource/use-react-hooks'; import { useConfirm } from '@frontend-opensource/use-react-hooks';
import { useAlertStore } from '@/shared/lib'; import { useAlertStore, useGlobalProgressStore } 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 { statisticsInputPublicSewageFacilityApiService } from '@/entities/statistics/api/input/statistics-public-sewage-facility-input-service'; import { statisticsInputPublicSewageFacilityApiService } from '@/entities/statistics/api/input/statistics-public-sewage-facility-input-service';
import { statisticsApi } from '@/entities/statistics/api/statistics-service'; import { statisticsApi } from '@/entities/statistics/api/statistics-service';
@@ -10,6 +10,8 @@ import type {
PublicSewageFacilityRequestParams, PublicSewageFacilityRequestParams,
PublicSewageFacilitySaveItem, PublicSewageFacilitySaveItem,
PublicSewageFacilitySaveRequest, PublicSewageFacilitySaveRequest,
PublicSewageMainFacilityMappingSaveItem,
PublicSewageMainFacilityMappingSaveRequest,
PublicSewageMappingTargetsRequestParams, PublicSewageMappingTargetsRequestParams,
} from '@/entities/statistics/model/input/statistics-public-sewage-facility-input-interface'; } from '@/entities/statistics/model/input/statistics-public-sewage-facility-input-interface';
import { import {
@@ -23,6 +25,7 @@ import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from '
import { getUserInfo } from '../../../../../../packages/common/components/src/api'; import { getUserInfo } from '../../../../../../packages/common/components/src/api';
import { MainFacilityMappingSaveDialog } from './MainFacilityMappingSaveDialog';
import SubmitDailog from './SubmitDailog'; import SubmitDailog from './SubmitDailog';
import CancelSubmitButton from '@/components/button/CancelSubmitButton'; import CancelSubmitButton from '@/components/button/CancelSubmitButton';
@@ -30,9 +33,12 @@ import NotApplicableCheckButton from '@/components/button/NotApplicableCheckButt
import SaveButton from '@/components/button/SaveButton'; import SaveButton from '@/components/button/SaveButton';
import SubmitButton from '@/components/button/SumitButton'; 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 DataGrid from '@/components/module/DataGrid'; import DataGrid from '@/components/module/DataGrid';
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 { VerificationResultDialog } from '@/components/module/VerificationResultDialog';
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 { doList } from '@/components/util/column/input/DoList';
@@ -93,12 +99,14 @@ async function waitForImrJobCompleted(
options?: { options?: {
pollIntervalMs?: number; pollIntervalMs?: number;
maxAttempts?: number; maxAttempts?: number;
onProgress?: (jobProgress: number) => void;
}, },
): Promise<void> { ): Promise<void> {
const pollIntervalMs = options?.pollIntervalMs ?? 2000; const pollIntervalMs = options?.pollIntervalMs ?? 2000;
const maxAttempts = options?.maxAttempts ?? 150; const maxAttempts = options?.maxAttempts ?? 150;
for (let i = 0; i < maxAttempts; i++) { for (let i = 0; i < maxAttempts; i++) {
const res = await getStatus(jobId); const res = await getStatus(jobId);
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0)));
if (res.status === 'COMPLETED' && Number(res.job_prog) === 100) return; if (res.status === '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 || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`);
@@ -108,11 +116,31 @@ async function waitForImrJobCompleted(
throw new Error('IMR 검증 완료 대기 시간이 초과되었습니다.'); throw new Error('IMR 검증 완료 대기 시간이 초과되었습니다.');
} }
type MainFacilityMappingSaveDraft = {
statsYr: string;
sewerBzmnCd: string;
admdstCd: string;
admdstNm: string;
mainFcltSn: string;
mainFcltNm: string;
initialCnsgnCstVl: number;
saveList: PublicSewageMainFacilityMappingSaveItem[];
deleteList: string[];
initialPrxyBzentyNmCn: string;
initialWholNope: number;
initialPrxyCtrtCn: string;
};
// 하수도 통계 관리 > 06. 공공하수처리시설 // 하수도 통계 관리 > 06. 공공하수처리시설
const PublicSewageFacilityInp = () => { const PublicSewageFacilityInp = () => {
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { isAdminInfo } = useAdminStore(); const { isAdminInfo } = useAdminStore();
const showGlobalProgress = useGlobalProgressStore((state) => state.show);
const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress);
const setGlobalProgressLabel = useGlobalProgressStore((state) => state.setLabel);
const hideGlobalProgress = useGlobalProgressStore((state) => state.hide);
const globalProgressVisible = useGlobalProgressStore((state) => state.visible);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [hasSearched, setHasSearched] = useState(false); const [hasSearched, setHasSearched] = useState(false);
const [isNotApplicable, setIsNotApplicable] = useState(false); const [isNotApplicable, setIsNotApplicable] = useState(false);
@@ -126,9 +154,16 @@ const PublicSewageFacilityInp = () => {
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]); const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
const [showDailog, setShowDailog] = useState<boolean>(false); const [showDailog, setShowDailog] = useState<boolean>(false);
const [selectedMainFacilitySn, setSelectedMainFacilitySn] = useState<string | null>(null); const [selectedMainFacilitySn, setSelectedMainFacilitySn] = useState<string | null>(null);
const mainFacilityMappingSaveDraftRef = useRef<MainFacilityMappingSaveDraft | null>(null);
const [mainFacilityMappingSaveDialogOpen, setMainFacilityMappingSaveDialogOpen] = useState(false);
const [mainFacilityMappingSaveDialogKey, setMainFacilityMappingSaveDialogKey] = useState(0);
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
const periodEnterableRef = useRef(true); const periodEnterableRef = useRef(true);
const searchDoneRef = useRef(false); const searchDoneRef = useRef(false);
/** ST00060001만: RealGrid 편집값이 state에 없을 수 있어 저장/제출 시 그리드에서 직접 읽음 */
const st60001GridGetRowsRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const submitFunc = () => { const submitFunc = () => {
void (async () => { void (async () => {
@@ -140,7 +175,8 @@ const PublicSewageFacilityInp = () => {
alert('먼저 조회를 진행해주세요.'); alert('먼저 조회를 진행해주세요.');
return; return;
} }
const currentData = rowDataMap.get('ST00060001') ?? []; const currentData =
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
if (!currentData || currentData.length === 0) { if (!currentData || currentData.length === 0) {
alert('제출할 데이터가 없습니다.'); alert('제출할 데이터가 없습니다.');
return; return;
@@ -199,6 +235,11 @@ const PublicSewageFacilityInp = () => {
return noApplicableVisible ? Boolean(isNotApplicable) : false; return noApplicableVisible ? Boolean(isNotApplicable) : false;
}; };
const isCheckedRow = (row: Record<string, string | number>): boolean => {
const v = row.__checked;
return v === 1 || v === '1' || v === 'Y' || v === 'y' || v === 'true';
};
const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => { const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList); const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
if (verificationBlocked) { if (verificationBlocked) {
@@ -213,125 +254,225 @@ const PublicSewageFacilityInp = () => {
alert('먼저 조회를 진행해주세요.'); alert('먼저 조회를 진행해주세요.');
return; return;
} }
const currentData = rowDataMap.get('ST00060001') ?? []; const currentData =
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
if (!currentData || currentData.length === 0) { if (!currentData || currentData.length === 0) {
alert('제출할 데이터가 없습니다.'); alert('제출할 데이터가 없습니다.');
return; return;
} }
const userInfo = await getUserInfo();
const submitList = buildFacilitySaveList(currentData);
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const statsCdForTab = tabStatsCdMap.get(activeTab) ?? activeTab;
const requestData = {
statsYr: lastSearchedValues.year ?? '',
ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
userId: userInfo.userUnqId,
noApplicable: getNoApplicableForRequest(),
laePicNm: submitInfo.laePicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
submitList,
};
try { try {
setIsLoading(true); showGlobalProgress({ label: '서버에 요청 중...', progress: 0 });
const response = await statisticsInputPublicSewageFacilityApiService.submitPublicSewageFacility(requestData);
if (response.status === 'success') {
if (response.data?.finalStatus === '5') {
const imrParam = {
user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? '',
stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? '',
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === '4' ? '4' : '2',
gen_verify_stts_cd: response.data?.nvpassed ? '1' : '2',
};
try {
const imrResponse = await statisticsApi.callImrService(imrParam);
if (imrResponse.job_id) {
await waitForImrJobCompleted(
imrResponse.job_id,
(id) => statisticsApi.getImrStatus(id),
);
}
alert('제출되었습니다.');
} catch {
alert('제출은 완료되었으나 IMR 검증 처리 중 오류가 발생했습니다.');
}
} else {
alert(response.message || '제출 처리 결과를 확인해주세요.');
}
await loadPublicSewageFacilityActiveTabData(
'ST00060001',
searchParamRef.current,
lastSearchedValues,
);
} else {
alert(response.message || '데이터 제출 중 오류가 발생했습니다.');
}
} finally {
setIsLoading(false);
}
};
const saveFunc = () => {
void (async () => {
if (activeTab !== 'ST00060001') {
alert('ST00060002 저장 API 연동 전입니다.');
return;
}
if (!lastSearchedValues || !searchParamRef.current) {
alert('먼저 조회를 진행해주세요.');
return;
}
const currentData = rowDataMap.get('ST00060001') ?? [];
if (!currentData || currentData.length === 0) {
alert('저장할 데이터가 없습니다.');
return;
}
if (!(await confirm("저장 하시겠습니까?"))) return;
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const saveList = buildFacilitySaveList(currentData); const submitList = buildFacilitySaveList(currentData);
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const param: PublicSewageFacilitySaveRequest = { const statsCdForTab = tabStatsCdMap.get(activeTab) ?? activeTab;
const requestData = {
statsYr: lastSearchedValues.year ?? '', statsYr: lastSearchedValues.year ?? '',
ctpvNm: lastSearchedValues.sido ?? '', ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '', sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
userId: userInfo.userUnqId, userId: userInfo.userUnqId,
superAdmin: isAdminInfo ? 'superAdmin' : '',
noApplicable: getNoApplicableForRequest(), noApplicable: getNoApplicableForRequest(),
saveList, laePicNm: submitInfo.laePicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
submitList,
}; };
try { setGlobalProgressLabel('일반 검증 진행 중...');
setIsLoading(true); const response =
const response = await statisticsInputPublicSewageFacilityApiService.savePublicSewageFacility(param); await statisticsInputPublicSewageFacilityApiService.submitPublicSewageFacility(
if (response.status === 'success') { requestData,
alert('저장되었습니다.'); );
if (response.status !== 'success') {
alert(response.message || '데이터 제출 중 오류가 발생했습니다.');
return;
}
const finalStatus = response.data?.finalStatus;
const nvpassed = response.data?.nvpassed;
if (finalStatus === '5') {
setGlobalProgress(50);
setGlobalProgressLabel('IMR 검증 진행 중...');
const imrParam = {
user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? '',
stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? '',
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === '4' ? '4' : '2',
gen_verify_stts_cd: nvpassed ? '1' : '2',
};
try {
const imrResponse = await statisticsApi.callImrService(imrParam);
if (imrResponse.job_id) {
await waitForImrJobCompleted(
imrResponse.job_id,
(id) => statisticsApi.getImrStatus(id),
{
onProgress: (jobProgress) => {
setGlobalProgress(50 + Math.round(jobProgress / 2));
},
},
);
}
setGlobalProgress(100);
await loadPublicSewageFacilityActiveTabData( await loadPublicSewageFacilityActiveTabData(
'ST00060001', 'ST00060001',
searchParamRef.current, searchParamRef.current,
lastSearchedValues, lastSearchedValues,
); );
} else { setVerificationResultDialogOpen(true);
alert(response.message || '데이터 저장 중 오류가 발생했습니다.'); } catch (e) {
await loadPublicSewageFacilityActiveTabData(
'ST00060001',
searchParamRef.current,
lastSearchedValues,
);
setVerificationResultDialogOpen(true);
const msg =
e instanceof Error && e.message
? e.message
: 'IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.';
alert(msg);
} }
} finally { } else {
setIsLoading(false); setGlobalProgress(100);
await loadPublicSewageFacilityActiveTabData(
'ST00060001',
searchParamRef.current,
lastSearchedValues,
);
setVerificationResultDialogOpen(true);
} }
} catch {
alert('데이터 제출 중 오류가 발생했습니다.');
} finally {
hideGlobalProgress();
}
};
const saveFunc = () => {
void (async () => {
if (!lastSearchedValues || !searchParamRef.current) {
alert('먼저 조회를 진행해주세요.');
return;
}
if (activeTab === 'ST00060002') {
if (!(await confirm('저장 하시겠습니까?'))) return;
const leftRows = rowDataMap.get('MainFacility') ?? [];
const rightRows = rowDataMap.get('MainFacilityTarget') ?? [];
if (!leftRows.length || !rightRows.length) {
alert('저장할 데이터가 없습니다.');
return;
}
if (!selectedMainFacilitySn) {
alert('좌측에서 중심 처리 시설을 먼저 선택해주세요.');
return;
}
const selectedMain = leftRows.find((r) => String(r.fcltSn ?? '') === selectedMainFacilitySn);
if (!selectedMain) {
alert('선택된 중심 처리 시설 정보를 찾을 수 없습니다.');
return;
}
const saveList: PublicSewageMainFacilityMappingSaveItem[] = rightRows
.filter((r) => isCheckedRow(r))
.map((r) => ({
fcltSn: String(r.fcltSn ?? ''),
admdstCd: String(r.admdstCd ?? ''),
admdstNm: String(r.admdstNm ?? ''),
swtrmtFcltCd: String(r.swtrmtFcltCd ?? ''),
swtrmtFcltNm: String(r.swtrmtFcltNm ?? ''),
isMainYn: String(r.isMainYn ?? ''),
}));
const deleteList = rightRows
.filter((r) => String(r.mappedYn ?? '') === 'Y' && !isCheckedRow(r))
.map((r) => String(r.fcltSn ?? ''))
.filter((sn) => sn !== '');
const wholNopeFromRow = Number(selectedMain.wholNope ?? 0);
mainFacilityMappingSaveDraftRef.current = {
statsYr: lastSearchedValues.year ?? '',
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
admdstCd: String(selectedMain.admdstCd ?? ''),
admdstNm: String(selectedMain.admdstNm ?? ''),
mainFcltSn: String(selectedMain.fcltSn ?? ''),
mainFcltNm: String(selectedMain.swtrmtFcltNm ?? ''),
initialCnsgnCstVl: Number(selectedMain.cnsgnCstVl ?? 0),
saveList,
deleteList,
initialPrxyBzentyNmCn: String(selectedMain.prxyBzentyNmCn ?? ''),
initialWholNope: Number.isFinite(wholNopeFromRow) ? wholNopeFromRow : 0,
initialPrxyCtrtCn: String(selectedMain.prxyCtrtCn ?? ''),
};
setMainFacilityMappingSaveDialogKey((k) => k + 1);
setMainFacilityMappingSaveDialogOpen(true);
return;
}
if (!(await confirm('저장 하시겠습니까?'))) return;
if (activeTab === 'ST00060001') {
try {
showGlobalProgress({ label: '저장 진행 중...', progress: 0 });
const userInfo = await getUserInfo();
const currentData =
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
if (!currentData || currentData.length === 0) {
alert('저장할 데이터가 없습니다.');
return;
}
const saveList = buildFacilitySaveList(currentData);
const param: PublicSewageFacilitySaveRequest = {
statsYr: lastSearchedValues.year ?? '',
ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
userId: userInfo.userUnqId,
superAdmin: isAdminInfo ? 'superAdmin' : '',
noApplicable: getNoApplicableForRequest(),
saveList,
};
const response =
await statisticsInputPublicSewageFacilityApiService.savePublicSewageFacility(
param,
);
if (response.status === 'success') {
setGlobalProgress(50);
await loadPublicSewageFacilityActiveTabData(
'ST00060001',
searchParamRef.current,
lastSearchedValues,
);
setGlobalProgress(100);
alert('저장되었습니다.');
} else {
alert(response.message || '데이터 저장 중 오류가 발생했습니다.');
}
} catch {
alert('데이터 저장 중 오류가 발생했습니다.');
} finally {
hideGlobalProgress();
}
return;
}
alert('현재 탭 저장 API 연동 전입니다.');
})(); })();
}; };
@@ -489,6 +630,59 @@ const PublicSewageFacilityInp = () => {
} }
}; };
const handleMainFacilityMappingSaveConfirm = async (input: {
prxyBzentyNmCn: string;
cnsgnCstVl: number;
wholNope: number;
prxyCtrtCn: string;
}) => {
const draft = mainFacilityMappingSaveDraftRef.current;
if (!draft || !searchParamRef.current || !lastSearchedValues) {
setMainFacilityMappingSaveDialogOpen(false);
mainFacilityMappingSaveDraftRef.current = null;
return;
}
try {
showGlobalProgress({ label: '저장 진행 중...', progress: 0 });
const userInfo = await getUserInfo();
const param: PublicSewageMainFacilityMappingSaveRequest = {
statsYr: draft.statsYr,
sewerBzmnCd: draft.sewerBzmnCd,
admdstCd: draft.admdstCd,
admdstNm: draft.admdstNm,
mainFcltSn: draft.mainFcltSn,
mainFcltNm: draft.mainFcltNm,
prxyBzentyNmCn: input.prxyBzentyNmCn,
cnsgnCstVl: input.cnsgnCstVl,
wholNope: input.wholNope,
prxyCtrtCn: input.prxyCtrtCn,
userId: userInfo.userUnqId,
saveList: draft.saveList,
deleteList: draft.deleteList,
};
const response =
await statisticsInputPublicSewageFacilityApiService.saveMainFacilityMapping(param);
if (response.status === 'success') {
setGlobalProgress(50);
setMainFacilityMappingSaveDialogOpen(false);
mainFacilityMappingSaveDraftRef.current = null;
await loadPublicSewageFacilityActiveTabData(
'ST00060002',
searchParamRef.current,
lastSearchedValues,
);
setGlobalProgress(100);
alert('저장되었습니다.');
} else {
alert(response.message || '데이터 저장 중 오류가 발생했습니다.');
}
} catch {
alert('데이터 저장 중 오류가 발생했습니다.');
} finally {
hideGlobalProgress();
}
};
const handleSearch = async (values: SearchFormValues) => { const handleSearch = async (values: SearchFormValues) => {
if (!values.sido || values.sido === 'ALL') { if (!values.sido || values.sido === 'ALL') {
alert('시도를 선택해주세요.'); alert('시도를 선택해주세요.');
@@ -685,6 +879,30 @@ const PublicSewageFacilityInp = () => {
const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } = activeTabToolbar; const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } = activeTabToolbar;
const isMainFacilityTab = activeTab === 'ST00060002'; const isMainFacilityTab = activeTab === 'ST00060002';
const openVerificationHistoryDialog = () => {
const ctx = lastSearchedValues ?? searchValues;
const activeTabStatsCd = tabStatsCdMap.get(activeTab) ?? activeTab;
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !activeTabStatsCd) {
alert('먼저 조회를 진행해주세요.');
return;
}
setVerificationHistoryOpen(true);
};
const verificationQueryContext =
(() => {
const ctx = lastSearchedValues ?? searchValues;
const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab;
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
return {
sewerBzmnCd: ctx.bsnmCd,
ctpvNm: ctx.sido,
sggNm: ctx.gugun === 'ALL' ? '' : (ctx.gugun ?? ''),
statsYr: ctx.year,
statsCd,
};
})();
return ( return (
<> <>
<div className="relative"> <div className="relative">
@@ -697,6 +915,9 @@ const PublicSewageFacilityInp = () => {
{showActionToolbarCard && ( {showActionToolbarCard && (
<div className="card !py-0"> <div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]"> <div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
<VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog}
/>
{showEnterableToolbarButtons && !isMainFacilityTab && ( {showEnterableToolbarButtons && !isMainFacilityTab && (
<> <>
{showIsNotApplicable && <NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />} {showIsNotApplicable && <NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />}
@@ -711,12 +932,13 @@ const PublicSewageFacilityInp = () => {
</div> </div>
</div> </div>
)} )}
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center"> {isLoading && !globalProgressVisible && (
<div className="relative size-[5rem]"> <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<LoadingWithAnimation color="primary" /> <div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div> </div>
</div> )}
}
<Tabs value={activeTab} onValueChange={(v) => void handleActiveTabChange(v)}> <Tabs value={activeTab} onValueChange={(v) => void handleActiveTabChange(v)}>
<TabsList> <TabsList>
{PublicSewageFactInpColumnList.map((tab) => ( {PublicSewageFactInpColumnList.map((tab) => (
@@ -753,6 +975,38 @@ const PublicSewageFacilityInp = () => {
? leftMainFacilityCellStyleCallback ? leftMainFacilityCellStyleCallback
: undefined : undefined
} }
rightOnItemChecked={
tab.tabKey === 'ST00060002'
? (itemIndex, checked) => {
setRowDataMap((prev) => {
const next = new Map(prev);
const rows = [...(next.get('MainFacilityTarget') ?? [])];
if (!rows[itemIndex]) return prev;
rows[itemIndex] = {
...rows[itemIndex],
__checked: checked ? 'Y' : '',
};
next.set('MainFacilityTarget', rows);
return next;
});
}
: undefined
}
rightOnItemAllChecked={
tab.tabKey === 'ST00060002'
? (checked) => {
setRowDataMap((prev) => {
const next = new Map(prev);
const rows = [...(next.get('MainFacilityTarget') ?? [])].map((r) => ({
...r,
__checked: checked ? 'Y' : '',
}));
next.set('MainFacilityTarget', rows);
return next;
});
}
: undefined
}
setLeftRowData={(updater) => { setLeftRowData={(updater) => {
setRowDataMap((prev) => { setRowDataMap((prev) => {
const next = new Map(prev); const next = new Map(prev);
@@ -803,6 +1057,9 @@ const PublicSewageFacilityInp = () => {
return next; return next;
}); });
}} }}
getGridDataRef={
tab.tabKey === 'ST00060001' ? st60001GridGetRowsRef : undefined
}
showHeaderCheckbox={false} showHeaderCheckbox={false}
enableSort={false} enableSort={false}
/> />
@@ -811,6 +1068,11 @@ const PublicSewageFacilityInp = () => {
})} })}
</Tabs> </Tabs>
</div> </div>
<VerificationResultHistoryDialog
open={verificationHistoryOpen}
onOpenChange={setVerificationHistoryOpen}
queryContext={verificationQueryContext}
/>
<SubmitDailog <SubmitDailog
open={showDailog} open={showDailog}
onOpenChange={setShowDailog} onOpenChange={setShowDailog}
@@ -819,6 +1081,26 @@ const PublicSewageFacilityInp = () => {
submitMode="normal" submitMode="normal"
isLoading={isLoading} isLoading={isLoading}
/> />
<VerificationResultDialog
open={verificationResultDialogOpen}
onOpenChange={setVerificationResultDialogOpen}
queryContext={verificationQueryContext}
/>
<MainFacilityMappingSaveDialog
key={`main-facility-mapping-save-${mainFacilityMappingSaveDialogKey}`}
open={mainFacilityMappingSaveDialogOpen}
onOpenChange={(open) => {
setMainFacilityMappingSaveDialogOpen(open);
if (!open) {
mainFacilityMappingSaveDraftRef.current = null;
}
}}
initialPrxyBzentyNmCn={mainFacilityMappingSaveDraftRef.current?.initialPrxyBzentyNmCn ?? ''}
initialCnsgnCstVl={mainFacilityMappingSaveDraftRef.current?.initialCnsgnCstVl ?? 0}
initialWholNope={mainFacilityMappingSaveDraftRef.current?.initialWholNope ?? 0}
initialPrxyCtrtCn={mainFacilityMappingSaveDraftRef.current?.initialPrxyCtrtCn ?? ''}
onConfirm={handleMainFacilityMappingSaveConfirm}
/>
</> </>
); );
}; };
@@ -1,71 +1,74 @@
import { useState, useMemo, useRef, useEffect, useLayoutEffect } from "react"; import { useEffect, useLayoutEffect,useMemo, useRef, useState } from "react";
import SearchForm from "@/components/module/SearchForm"; import { useConfirm } from "@frontend-opensource/use-react-hooks";
import DataGrid from "@/components/module/DataGrid";
import SplitDataGrid from "@/components/module/SplitDataGrid";
import GridTopActionButton from "@/components/module/GridTopActionButton";
import SubmitButton from "@/components/button/SumitButton";
import SaveButton from "@/components/button/SaveButton";
import LastYearDataButton from "@/components/button/LastYearDataButton";
import VerificationResultButton from "@/components/button/VerificationResultButton";
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import SubmitDailog from "./SubmitDailog";
import { SewagePipeInpColumnInfoList } from "@/components/util/column/input/sewagePipe/SewagePipeInpColumn";
import type { ColumnInfo, ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { useAlertStore, useGlobalProgressStore } from "@/shared/lib"; import { useAlertStore, useGlobalProgressStore } from "@/shared/lib";
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 { useAdminStore } from "@/store/admin-store";
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 { useConfirm } from "@frontend-opensource/use-react-hooks";
import { doList } from "@/components/util/column/input/DoList";
import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service"; import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { import type {
SeweragePipeInputFactInstallStatusSaveRequest, SeweragePipeInputFactInstallStatusSaveRequest,
SeweragePipeInputFactInstallStatusSubmitRequest, SeweragePipeInputFactInstallStatusSubmitRequest,
SeweragePipeInputManholeStatusItem, SeweragePipeInputManholeStatusItem,
SeweragePipeInputManholeStatusSaveYearlyStatItem,
SeweragePipeInputManholeStatusSaveRequest, SeweragePipeInputManholeStatusSaveRequest,
SeweragePipeInputManholeStatusSaveYearlyStatItem,
SeweragePipeInputManholeStatusSubmitRequest, SeweragePipeInputManholeStatusSubmitRequest,
SeweragePipeInputPipeDiameterStatusSaveRequest, SeweragePipeInputPipeDiameterStatusSaveRequest,
SeweragePipeInputPipeDiameterStatusSubmitRequest, SeweragePipeInputPipeDiameterStatusSubmitRequest,
SeweragePipeInputPipeTypeStatusItem, SeweragePipeInputPipeTypeStatusItem,
SeweragePipeInputPipeTypeStatusSaveRequest, SeweragePipeInputPipeTypeStatusSaveRequest,
SeweragePipeInputPipeTypeStatusSubmitRequest, SeweragePipeInputPipeTypeStatusSubmitRequest,
SeweragePipeInputRegionalFactStatusSaveRequest,
SeweragePipeInputRegionalFactStatusSaveItem, SeweragePipeInputRegionalFactStatusSaveItem,
SeweragePipeInputRegionalFactStatusSaveRequest,
SeweragePipeInputRegionalFactStatusSubmitRequest, SeweragePipeInputRegionalFactStatusSubmitRequest,
SeweragePipeInputSubmitResponse, SeweragePipeInputSubmitResponse,
SeweragePipeInputVerificationResult, SeweragePipeInputVerificationResult,
SeweragePipeRequestParams, SeweragePipeRequestParams,
} from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface"; } from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface";
import {
type CheckInputPeriodItem,
getVerificationPeriodActionBlockedMessage,
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 {
cancelSubmitRequest, cancelSubmitRequest,
ImrStatusResponse, ImrStatusResponse,
SewerageStatusInfo, SewerageStatusInfo,
SubmitExceptionRequest, SubmitExceptionRequest,
} 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 { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import {
type CheckInputPeriodItem, import { getUserInfo } from "../../../../../../packages/common/components/src/api";
getVerificationPeriodActionBlockedMessage,
isInputPeriodCurrent, import SubmitDailog from "./SubmitDailog";
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
import LastYearDataButton from "@/components/button/LastYearDataButton";
import SaveButton from "@/components/button/SaveButton";
import SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton";
import DataGrid from "@/components/module/DataGrid";
import GridTopActionButton from "@/components/module/GridTopActionButton";
import SearchForm from "@/components/module/SearchForm";
import SplitDataGrid from "@/components/module/SplitDataGrid";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import type { ColumnConfig,ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { doList } from "@/components/util/column/input/DoList";
import { FactInstallStatusInpColumn } from "@/components/util/column/input/sewagePipe/FactInstallStatusInpColumn"; import { FactInstallStatusInpColumn } from "@/components/util/column/input/sewagePipe/FactInstallStatusInpColumn";
import { ManholeStatusInpColumn } from "@/components/util/column/input/sewagePipe/ManholeStatusInpColumn"; import { ManholeStatusInpColumn } from "@/components/util/column/input/sewagePipe/ManholeStatusInpColumn";
import { PipeDiameterStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeDiameterStatusInpColumn"; import { PipeDiameterStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeDiameterStatusInpColumn";
import { PipeTypeStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeTypeStatusInpColumn"; import { PipeTypeStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeTypeStatusInpColumn";
import { RegionalFactStatusInpColumn } from "@/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn"; import { RegionalFactStatusInpColumn } from "@/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn";
import { SewagePipeInpColumnInfoList } from "@/components/util/column/input/sewagePipe/SewagePipeInpColumn";
import { toGridRows } from "@/components/util/grid/gridFunction"; import { toGridRows } from "@/components/util/grid/gridFunction";
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { useAdminStore } from "@/store/admin-store";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
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";
@@ -740,7 +743,7 @@ const SewagePipeInp = () => {
if (!job) return; if (!job) return;
postVerificationSearchRef.current = null; postVerificationSearchRef.current = null;
void handleSearch(job.values, { forcedTabKey: job.tabKey }); void handleSearch(job.values, { forcedTabKey: job.tabKey });
// eslint-disable-next-line react-hooks/exhaustive-deps -- 검색폼 remount 직후 1회 자동 조회
}, [searchFormRemountKey]); }, [searchFormRemountKey]);
const handleActiveTabChange = async (tabKey: string) => { const handleActiveTabChange = async (tabKey: string) => {
@@ -1209,6 +1212,7 @@ const SewagePipeInp = () => {
true, true,
false, false,
); );
setVerificationResultDialogOpen(true);
const msg = const msg =
e instanceof Error && e.message e instanceof Error && e.message
? e.message ? e.message
@@ -1224,7 +1228,7 @@ const SewagePipeInp = () => {
true, true,
false, false,
); );
alert("제출되었습니다."); setVerificationResultDialogOpen(true);
} }
} catch { } catch {
alert("데이터 제출 중 오류가 발생했습니다."); alert("데이터 제출 중 오류가 발생했습니다.");
@@ -1,52 +1,54 @@
import { useState, useMemo, useRef, useLayoutEffect, useEffect } from "react"; import { useEffect,useLayoutEffect, useMemo, useRef, useState } from "react";
import SearchForm from "@/components/module/SearchForm"; import { useConfirm } from "@frontend-opensource/use-react-hooks";
// import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid";
import SubmitButton from "@/components/button/SumitButton";
import SaveButton from "@/components/button/SaveButton";
import LastYearDataButton from "@/components/button/LastYearDataButton";
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { LoadingWithAnimation } from "@common/components";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import SubmitDailog from "./SubmitDailog";
import { useAlertStore, useGlobalProgressStore } from "@/shared/lib"; import { useAlertStore, useGlobalProgressStore } from "@/shared/lib";
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 { useAdminStore } from "@/store/admin-store"; 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 { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { toGridRows } from "@/components/util/grid/gridFunction";
import { getFooterSummary } from "@/components/util/grid/footerSummary";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { import {
type CheckInputPeriodItem, type CheckInputPeriodItem,
getVerificationPeriodActionBlockedMessage,
isInputPeriodCurrent,
type SewerageSupplyRateItem, type SewerageSupplyRateItem,
type SewerageSupplyRateRequestParams, type SewerageSupplyRateRequestParams,
type SewerageSupplyRateSaveItem, type SewerageSupplyRateSaveItem,
type SewerageSupplyRateVerificationResult,
type SewerageSupplyRateSaveRequest, type SewerageSupplyRateSaveRequest,
getVerificationPeriodActionBlockedMessage, type SewerageSupplyRateVerificationResult,
isInputPeriodCurrent,
} 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 { import type {
cancelSubmitRequest, cancelSubmitRequest,
ImrStatusResponse, ImrStatusResponse,
SewerageStatusInfo, SewerageStatusInfo,
SubmitExceptionRequest, SubmitExceptionRequest,
} 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 { LoadingWithAnimation } from "@common/components";
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import SubmitDailog from "./SubmitDailog";
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
import LastYearDataButton from "@/components/button/LastYearDataButton";
import SaveButton from "@/components/button/SaveButton";
import SubmitButton from "@/components/button/SumitButton";
import VerificationResultButton from "@/components/button/VerificationResultButton";
// import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { doList } from "@/components/util/column/input/DoList"; import { doList } from "@/components/util/column/input/DoList";
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
import { getFooterSummary } from "@/components/util/grid/footerSummary";
import { toGridRows } from "@/components/util/grid/gridFunction";
import { 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";
@@ -274,7 +276,7 @@ const SeweragePenetrationRateInp = () => {
if (!values) return; if (!values) return;
postVerificationSearchRef.current = null; postVerificationSearchRef.current = null;
void handleSearch(values); void handleSearch(values);
// eslint-disable-next-line react-hooks/exhaustive-deps -- 검색폼 remount 직후 1회 자동 조회
}, [searchFormRemountKey]); }, [searchFormRemountKey]);
const SubmitExceptionFunc = async (submitInfo: StatisticsInputSubmitInfoFormData) => { const SubmitExceptionFunc = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
@@ -475,6 +477,7 @@ const SeweragePenetrationRateInp = () => {
} catch (e) { } catch (e) {
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
setIsLoading(false); setIsLoading(false);
setVerificationResultDialogOpen(true);
const msg = const msg =
e instanceof Error && e.message e instanceof Error && e.message
? e.message ? e.message
@@ -485,7 +488,7 @@ const SeweragePenetrationRateInp = () => {
} else { } else {
setGlobalProgress(100); setGlobalProgress(100);
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false);
alert('제출되었습니다.'); setVerificationResultDialogOpen(true);
} }
} else { } else {
alert(response.message || '데이터 제출 중 오류가 발생했습니다.'); alert(response.message || '데이터 제출 중 오류가 발생했습니다.');
@@ -8,14 +8,15 @@ import {
type RowSelectionState, type RowSelectionState,
useReactTable, useReactTable,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import type { AxiosError } from "axios";
import { client } from "@/shared/api/client"; import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config"; import type { ApiErrorResponse } from "@/shared/model";
import { useAlertStore } from "@/shared/lib";
import { import {
type AdstZoneResponseDto, type AdstZoneResponseDto,
type BusinessSendRequest, type BusinessSendRequest,
type BusinessSendResponse, type BusinessSendResponse,
noticeTalkSendingApi,
type SelectedRecipient, type SelectedRecipient,
type SewerStUserItem, type SewerStUserItem,
useNtftalkTemplateList, useNtftalkTemplateList,
@@ -122,10 +123,26 @@ function NtitlkSndng() {
const [previewRecipient, setPreviewRecipient] = const [previewRecipient, setPreviewRecipient] =
useState<SelectedRecipient | null>(null); useState<SelectedRecipient | null>(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false); const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const [isSending, setIsSending] = useState(false);
const [userListRowSelection, setUserListRowSelection] = const [userListRowSelection, setUserListRowSelection] =
useState<RowSelectionState>({}); useState<RowSelectionState>({});
const sendMutation = useMutationWrapper<
BusinessSendResponse,
AxiosError<ApiErrorResponse>,
BusinessSendRequest
>({
mutationFn: (payload) => noticeTalkSendingApi.sendBusiness(payload),
onSuccess: (data) => {
if (!data.success) {
alert(data.message || "알림톡 발송 요청에 실패했습니다.");
return;
}
alert(data.message || "알림톡 발송이 요청되었습니다.");
setSelectedRecipients([]);
setCommonVariableValues({});
},
});
const { data: templatePageData } = useNtftalkTemplateList(); const { data: templatePageData } = useNtftalkTemplateList();
const templates = templatePageData?.content ?? []; const templates = templatePageData?.content ?? [];
@@ -321,7 +338,7 @@ function NtitlkSndng() {
}), }),
); );
if (missingVariable) { if (missingVariable) {
alert("필수 변수값이 입력되지 않은 발송 대상이 있습니다."); alert("변수값이 입력되지 않은 발송 대상이 있습니다.");
return; return;
} }
if ( if (
@@ -331,34 +348,13 @@ function NtitlkSndng() {
) )
return; return;
const payload: BusinessSendRequest = { sendMutation.mutate({
templateCode: selectedTemplateCode, templateCode: selectedTemplateCode,
callback: "032-590-4000", callback: "032-590-4000",
recipients: selectedRecipients.map((recipient) => recipients: selectedRecipients.map((recipient) =>
getMergedVariables(recipient, commonVariableValues), getMergedVariables(recipient, commonVariableValues),
), ),
}; });
try {
setIsSending(true);
const response = await client.post<BusinessSendResponse>(
`${ENDPOINT.NTFTALK_SERVICE.ATA_SEND}/business`,
payload,
);
const data = response.data;
if (!data.success) {
alert(data.message || "알림톡 발송 요청에 실패했습니다.");
return;
}
alert(data.message || `알림톡 발송이 요청되었습니다.`);
setSelectedRecipients([]);
setCommonVariableValues({});
} catch {
alert("알림톡 발송 중 오류가 발생했습니다.");
} finally {
setIsSending(false);
}
}; };
return ( return (
@@ -567,7 +563,8 @@ function NtitlkSndng() {
color="green" color="green"
onClick={handleSend} onClick={handleSend}
disabled={ disabled={
isSending || selectedRecipients.length === 0 sendMutation.isPending ||
selectedRecipients.length === 0
} }
> >
발송 발송
@@ -1,84 +1,230 @@
import { useState } from "react"; import { useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
StatsInputPeriodItem,
StatsInputPeriodListRequest,
} from "@/entities/statistics/model/statistics-interface";
import { Button, LoadingWithAnimation } from "@common/components";
import {
StatisticsInputPeriodDialog,
type StatisticsInputPeriodDialogValues,
} from "./StatisticsInputPeriodDialog";
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 { toGridRows } from "@/components/util/grid/gridFunction";
function seTypeToDisplayLabel(seType: StatsInputPeriodItem["seType"]): string {
if (seType === "INPT") return "입력";
if (seType === "VRFC") return "검증";
return seType;
}
function rowSeTypeToCode(row: Record<string, string | number>): "INPT" | "VRFC" {
const v = String(row.seType ?? "");
if (v === "INPT" || v === "입력") return "INPT";
if (v === "VRFC" || v === "검증") return "VRFC";
return "INPT";
}
/** API `YYYYMMDD` 등 → Date */
function parseYmdToDate(ymd: unknown): Date | undefined {
const s = String(ymd ?? "").replace(/\D/g, "");
if (s.length !== 8) return undefined;
const y = Number(s.slice(0, 4));
const m = Number(s.slice(4, 6));
const d = Number(s.slice(6, 8));
if (!y || m < 1 || m > 12 || d < 1 || d > 31) return undefined;
const dt = new Date(y, m - 1, d);
if (dt.getFullYear() !== y || dt.getMonth() !== m - 1 || dt.getDate() !== d) {
return undefined;
}
return dt;
}
const StatisticsInputManagement = () => { const StatisticsInputManagement = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const [isLoading, setIsLoading] = useState(false);
const [rowData, setRowData] = useState<Record<string, string | number>[]>( const [rowData, setRowData] = useState<Record<string, string | number>[]>(
[], [],
); );
const [periodDialogOpen, setPeriodDialogOpen] = useState(false);
const [periodDialogMode, setPeriodDialogMode] = useState<"edit" | "create">("edit");
const [createDefaultStatsYr, setCreateDefaultStatsYr] = useState("");
const [periodDialogInitial, setPeriodDialogInitial] =
useState<StatisticsInputPeriodDialogValues | null>(null);
const [searchFormValues, setSearchFormValues] = useState<SearchFormValues | null>(null);
const lastSearchRef = useRef<SearchFormValues | null>(null);
const handleSearch = async (values: SearchFormValues) => {
if (!values.year) {
alert("년도를 선택해주세요.");
return;
}
lastSearchRef.current = values;
try {
setIsLoading(true);
const params: StatsInputPeriodListRequest = {
statsYr: values.year,
};
if (values.clsfCd && values.clsfCd !== "ALL") {
params.seType = values.clsfCd;
}
const res = await statisticsApi.getStatsInputPeriodList(params);
if (res.status === "success" && Array.isArray(res.data)) {
const rowsForGrid = res.data.map((row) => ({
...row,
seType: seTypeToDisplayLabel(row.seType),
}));
setRowData(toGridRows(rowsForGrid));
} else {
setRowData([]);
alert(res.message || "데이터 조회에 실패했습니다.");
}
} catch {
setRowData([]);
alert("데이터 조회 중 오류가 발생했습니다.");
} finally {
setIsLoading(false);
}
};
const handleReset = () => {
setRowData([]);
};
const handleRegisterClick = () => {
const yr =
searchFormValues?.year ?? lastSearchRef.current?.year ?? "";
if (!yr) {
alert("검색 폼에서 년도를 선택해 주세요.");
return;
}
setPeriodDialogMode("create");
setCreateDefaultStatsYr(yr);
setPeriodDialogInitial(null);
setPeriodDialogOpen(true);
};
const handleRowClick = (row: Record<string, string | number>) => {
const seCdRaw = row.seCd;
const seCdNum =
typeof seCdRaw === "number"
? seCdRaw
: parseInt(String(seCdRaw ?? ""), 10);
setPeriodDialogMode("edit");
setPeriodDialogInitial({
statsYr: String(row.statsYr ?? ""),
seType: rowSeTypeToCode(row),
seCd: Number.isFinite(seCdNum) ? seCdNum : 0,
inptBgngYmd: parseYmdToDate(row.inptBgngYmd),
inptEndYmd: parseYmdToDate(row.inptEndYmd),
});
setPeriodDialogOpen(true);
};
const columns: ColumnConfig[] = [ const columns: ColumnConfig[] = [
{ {
name: "sido", name: "statsYr",
fieldName: "시도", fieldName: "statsYr",
header: { text: "시도" }, header: { text: "연도" },
mergeCells: true,
editable: false, editable: false,
sortdisable: true,
}, },
{ {
name: "sgg", name: "seType",
fieldName: "시군구", fieldName: "seType",
header: { text: "시군구" }, header: { text: "분류" },
mergeCells: true,
editable: false, editable: false,
sortdisable: true,
}, },
{ {
name: "구분", name: "seCd",
fieldName: "구분", fieldName: "seCd",
header: { text: "구분" }, header: { text: "구분" },
editable: false, editable: false,
sortdisable: true,
}, },
{ {
name: "광역자치단체", name: "inptBgngYmd",
fieldName: "광역자치단체", fieldName: "inptBgngYmd",
header: { text: "광역자치단체" }, header: { text: "시작일" },
editable: false, editable: false,
sortdisable: true,
}, },
{ {
name: "공단", name: "inptEndYmd",
fieldName: "공단", fieldName: "inptEndYmd",
header: { text: "공단" }, header: { text: "종료일" },
editable: false, editable: false,
sortdisable: true,
},
{
name: "처리내역",
fieldName: "처리내역",
header: { text: "처리내역" },
editable: false,
sortdisable: true,
}, },
]; ];
const columnLayout = [ const columnLayout = [
"sido", "statsYr",
"sgg", "seType",
"구분", "seCd",
{ "inptBgngYmd",
name: "groupA", "inptEndYmd",
hideChildHeaders: false,
header: { text: "검토내역" },
items: ["광역자치단체", "공단", "처리내역"],
},
]; ];
return ( return (
<> <div className="relative">
<SearchForm sido={false} /> <SearchForm
sido={false}
gugun={false}
clsf
onSubmit={handleSearch}
onReset={handleReset}
onValuesChange={setSearchFormValues}
/>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
<Button
color="primary"
className="h-[3.2rem] w-[96px]"
type="button"
onClick={handleRegisterClick}
>
<span>등록</span>
</Button>
</div>
</div>
<DataGrid <DataGrid
columnInfo={{ columns, columnLayout }} columnInfo={{ columns, columnLayout }}
rowData={rowData} rowData={rowData}
enableSort={false}
setRowData={setRowData} setRowData={setRowData}
isExcelImport={false} isExcelImport={false}
isExcelExport={false} isExcelExport={false}
onRowClick={handleRowClick}
/> />
</> <StatisticsInputPeriodDialog
open={periodDialogOpen}
mode={periodDialogMode}
createDefaultStatsYr={createDefaultStatsYr}
onOpenChange={(open) => {
setPeriodDialogOpen(open);
if (!open) {
setPeriodDialogInitial(null);
setPeriodDialogMode("edit");
}
}}
initial={periodDialogInitial}
onSaveSuccess={async () => {
const v = lastSearchRef.current;
if (v) await handleSearch(v);
}}
/>
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
</div>
); );
}; };
@@ -0,0 +1,315 @@
"use client";
import { useEffect, useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useAlertStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
StatsInputPeriodDeleteRequest,
StatsInputPeriodSaveRequest,
} from "@/entities/statistics/model/statistics-interface";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Input,
InputDatePicker,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
export type StatisticsInputPeriodDialogValues = {
statsYr: string;
seType: "INPT" | "VRFC";
seCd: number;
inptBgngYmd: Date | undefined;
inptEndYmd: Date | undefined;
};
export type StatisticsInputPeriodDialogMode = "edit" | "create";
function normalizeStatsYrInput(raw: string): string {
return raw.replace(/\D/g, "").slice(0, 4);
}
function formatDateToYmd(d: Date | undefined): string | null {
if (!d || Number.isNaN(d.getTime())) return null;
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}${m}${day}`;
}
export interface StatisticsInputPeriodDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
/** 행 클릭(수정) 시에만 전달. 등록 모드에서는 null */
initial: StatisticsInputPeriodDialogValues | null;
mode: StatisticsInputPeriodDialogMode;
/** mode가 create일 때 SearchForm의 연도 */
createDefaultStatsYr?: string;
/** 저장 성공 후 목록 재조회 등 */
onSaveSuccess?: () => void | Promise<void>;
}
export function StatisticsInputPeriodDialog({
open,
onOpenChange,
initial,
mode,
createDefaultStatsYr,
onSaveSuccess,
}: StatisticsInputPeriodDialogProps) {
const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm();
const [statsYr, setStatsYr] = useState("");
const [seType, setSeType] = useState<"INPT" | "VRFC" | null>(null);
const [seCdInput, setSeCdInput] = useState("");
const [inptBgngYmd, setInptBgngYmd] = useState<Date | undefined>(undefined);
const [inptEndYmd, setInptEndYmd] = useState<Date | undefined>(undefined);
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
if (!open) return;
if (mode === "create") {
setStatsYr(normalizeStatsYrInput(createDefaultStatsYr ?? ""));
setSeType(null);
setSeCdInput("0");
setInptBgngYmd(undefined);
setInptEndYmd(undefined);
return;
}
if (!initial) return;
setStatsYr(normalizeStatsYrInput(String(initial.statsYr ?? "")));
setSeType(initial.seType);
setSeCdInput(String(Number.isFinite(initial.seCd) ? initial.seCd : 0));
setInptBgngYmd(initial.inptBgngYmd);
setInptEndYmd(initial.inptEndYmd);
}, [open, mode, initial, createDefaultStatsYr]);
const handleSave = async () => {
const bgng = formatDateToYmd(inptBgngYmd);
const end = formatDateToYmd(inptEndYmd);
if (!bgng || !end) {
alert("시작일과 종료일을 선택해주세요.");
return;
}
if (bgng > end) {
alert("시작일은 종료일보다 늦을 수 없습니다.");
return;
}
if (seType === null) {
alert("분류를 선택해주세요.");
return;
}
const seCdParsed =
mode === "create"
? 0
: (() => {
const n = parseInt(seCdInput, 10);
return Number.isFinite(n) && n >= 0 ? n : NaN;
})();
if (mode === "edit" && !Number.isFinite(seCdParsed)) {
alert("구분 값이 올바르지 않습니다.");
return;
}
const yr = normalizeStatsYrInput(statsYr.trim());
if (mode === "create" && !/^\d{4}$/.test(yr)) {
alert("연도는 숫자 4자리로 입력해 주세요.");
return;
}
if (mode === "edit" && !yr) {
alert("연도가 없습니다.");
return;
}
try {
setIsSubmitting(true);
const userInfo = await getUserInfo();
const param: StatsInputPeriodSaveRequest = {
statsYr: yr,
seType,
seCd: seCdParsed,
inptBgngYmd: bgng,
inptEndYmd: end,
rgtrId: userInfo.userUnqId,
};
const res = await statisticsApi.saveStatsInputPeriod(param);
if (res.status === "success") {
alert(`${isCreate ? '등록 되었습니다.' : '수정 되었습니다.'}`);
onOpenChange(false);
await onSaveSuccess?.();
} else {
alert(res.message || "저장에 실패했습니다.");
}
} catch {
alert("저장 중 오류가 발생했습니다.");
} finally {
setIsSubmitting(false);
}
};
const handleDelete = async () => {
if (mode === "create" || !initial) return;
if (!(await confirm("삭제하시겠습니까?"))) return;
const bgng = formatDateToYmd(initial.inptBgngYmd);
const end = formatDateToYmd(initial.inptEndYmd);
if (!bgng || !end) {
alert("삭제할 기간 정보가 없습니다.");
return;
}
const yr = normalizeStatsYrInput(String(initial.statsYr ?? ""));
if (!yr) {
alert("연도가 없습니다.");
return;
}
const seCd =
Number.isFinite(initial.seCd) && initial.seCd >= 0 ? initial.seCd : 0;
try {
setIsSubmitting(true);
const userInfo = await getUserInfo();
const param: StatsInputPeriodDeleteRequest = {
statsYr: yr,
seType: initial.seType,
seCd,
inptBgngYmd: bgng,
inptEndYmd: end,
rgtrId: userInfo.userUnqId,
};
const res = await statisticsApi.deleteStatsInputPeriod(param);
if (res.status === "success") {
alert("삭제되었습니다.");
onOpenChange(false);
await onSaveSuccess?.();
} else {
alert(res.message || "삭제에 실패했습니다.");
}
} catch {
alert("삭제 중 오류가 발생했습니다.");
} finally {
setIsSubmitting(false);
}
};
const isCreate = mode === "create";
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[48rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>{isCreate ? "입력 기간 등록" : "입력 관리"}</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4 py-2">
<div className="flex flex-col gap-2">
<Label className="text-label">연도</Label>
{isCreate ? (
<Input
type="text"
inputMode="numeric"
maxLength={4}
autoComplete="off"
value={statsYr}
onChange={(e) =>
setStatsYr(normalizeStatsYrInput(e.target.value))
}
placeholder="예: 2025"
disabled={isSubmitting}
className="!text-[1.3rem]"
/>
) : (
<Input
value={statsYr}
readOnly
disabled
className="!text-[1.3rem]"
/>
)}
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">분류</Label>
<Select
value={seType ?? undefined}
onValueChange={(v) => setSeType(v as "INPT" | "VRFC")}
disabled={isSubmitting}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="INPT">입력</SelectItem>
<SelectItem value="VRFC">검증</SelectItem>
</SelectContent>
</Select>
</div>
{!isCreate && (
<div className="flex flex-col gap-2">
<Label className="text-label">구분</Label>
<Input
type="number"
inputMode="numeric"
min={0}
value={seCdInput}
disabled
className="!text-[1.3rem]"
/>
</div>
)}
<div className="flex flex-col gap-2">
<Label className="text-label">시작일</Label>
<InputDatePicker
value={inptBgngYmd}
onChange={setInptBgngYmd}
className="w-full"
/>
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">종료일</Label>
<InputDatePicker
value={inptEndYmd}
onChange={setInptEndYmd}
className="w-full"
/>
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button type="button" color="gray" disabled={isSubmitting}>
닫기
</Button>
</DialogClose>
{!isCreate && (
<Button
type="button"
color="red"
disabled={isSubmitting}
onClick={() => void handleDelete()}
>
삭제
</Button>
)}
<Button
type="button"
color="green"
disabled={isSubmitting}
onClick={() => void handleSave()}
>
{isCreate ? '등록' : '수정'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -9,8 +9,8 @@ import ApproveButton from "@/components/button/ApproveButton";
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 SaveButton from "@/components/button/SaveButton"; import SaveButton from "@/components/button/SaveButton";
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 { 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";
@@ -40,10 +40,12 @@ function getListDataByTab(tabKey: string, responseData: unknown): object[] {
return []; return [];
} }
if (tabKey === "ST00030001") { if (tabKey === "ST00030001") {
return (((responseData as { mtcStatusList?: unknown[] }).mtcStatusList) ?? []) as object[]; const list = (responseData as { sewerPipeMtcList?: unknown }).sewerPipeMtcList;
return Array.isArray(list) ? (list as object[]) : [];
} }
if (tabKey === "ST00030002") { if (tabKey === "ST00030002") {
return (((responseData as { drdgStatusList?: unknown[] }).drdgStatusList) ?? []) as object[]; const list = (responseData as { sewerPipeDrdgList?: unknown }).sewerPipeDrdgList;
return Array.isArray(list) ? (list as object[]) : [];
} }
return []; return [];
} }
@@ -1,16 +1,18 @@
import { useRef, useState } from "react"; import { useMemo, useRef, useState } from "react";
import { useAlertStore } from "@/shared/lib"; import { useAlertStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import type { SewagePipeReviewRequestParams } from "@/entities/statistics/model/statistics-sewage-pipe-review-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 ApproveButton from "@/components/button/ApproveButton"; 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 SaveButton from "@/components/button/SaveButton";
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 { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn"; import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
@@ -37,20 +39,73 @@ async function fetchSewagePipeReviewTab(
return null; return null;
} }
function extractReviewRowsFromResponseData(
tabKey: string,
data: unknown,
): object[] {
if (Array.isArray(data)) return data as object[];
if (!data || typeof data !== "object") return [];
const d = data as Record<string, unknown>;
switch (tabKey) {
case "ST00020001":
return Array.isArray(d.regionalStatusList) ? (d.regionalStatusList as object[]) : [];
case "ST00020002":
return Array.isArray(d.facilityStatusList) ? (d.facilityStatusList as object[]) : [];
case "ST00020003":
return Array.isArray(d.pipeTypeList) ? (d.pipeTypeList as object[]) : [];
case "ST00020004":
return Array.isArray(d.pipeDiamList) ? (d.pipeDiamList as object[]) : [];
default:
return [];
}
}
function extractStatsStatusInfo(data: unknown): StatsStatusInfo | null {
if (!data || typeof data !== "object") return null;
const ssi = (data as { statsStatusInfo?: StatsStatusInfo }).statsStatusInfo;
return ssi && typeof ssi === "object" ? ssi : null;
}
// 하수도 통계 검토 02.하수관로 // 하수도 통계 검토 02.하수관로
const SewagePipeRvw = () => { const SewagePipeRvw = () => {
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map()); const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [activeTab, setActiveTab] = useState<string>(firstTabKey); const [activeTab, setActiveTab] = useState<string>(firstTabKey);
/** 탭별 조회 응답의 statsStatusInfo — 활성 탭에 맞춰 버튼 노출 (SewagePipeInp의 tabStatusInfoMap과 동일 패턴) */
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
Map<string, StatsStatusInfo>
>(new Map());
const searchParamRef = useRef<SewagePipeReviewRequestParams | null>(null); const searchParamRef = useRef<SewagePipeReviewRequestParams | null>(null);
const searchDoneRef = useRef(false); const searchDoneRef = useRef(false);
const lazyTabLoggedRef = useRef<Set<string>>(new Set()); const lazyTabLoggedRef = useRef<Set<string>>(new Set());
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
const statsStatusInfo = tabStatsStatusInfoMap.get(activeTab) ?? {};
const submit =
statsStatusInfo.submitStatusCd != null
? String(statsStatusInfo.submitStatusCd)
: "";
const prvn =
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
const submitIs3 = submit === "3";
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
return {
showReviewOpinionButton: reviewSubmitVisible,
showSubmitButton: reviewSubmitVisible,
showReturnButton: submitIs3 && prvn !== "3",
};
}, [tabStatsStatusInfoMap, activeTab]);
const showActionBar =
showReviewOpinionButton || showReturnButton || showSubmitButton;
const handleReset = () => { const handleReset = () => {
setRowDataMap(new Map()); setRowDataMap(new Map());
setActiveTab(firstTabKey); setActiveTab(firstTabKey);
setTabStatsStatusInfoMap(new Map());
searchParamRef.current = null; searchParamRef.current = null;
searchDoneRef.current = false; searchDoneRef.current = false;
lazyTabLoggedRef.current.clear(); lazyTabLoggedRef.current.clear();
@@ -79,15 +134,24 @@ const SewagePipeRvw = () => {
const response = await fetchSewagePipeReviewTab(activeTab, searchParam); const response = await fetchSewagePipeReviewTab(activeTab, searchParam);
if (response?.status === "success" && response.data) { if (response?.status === "success" && response.data) {
const ssi = extractStatsStatusInfo(response.data);
setTabStatsStatusInfoMap((prev) => {
const next = new Map(prev);
next.set(activeTab, ssi ?? {});
return next;
});
const rows = extractReviewRowsFromResponseData(activeTab, response.data);
setRowDataMap((prev) => { setRowDataMap((prev) => {
const next = new Map(prev); const next = new Map(prev);
next.set(activeTab, toGridRows(response.data as object[])); next.set(activeTab, toGridRows(rows));
return next; return next;
}); });
} else { } else {
setTabStatsStatusInfoMap(new Map());
alert(response?.message || "데이터 조회 중 오류가 발생했습니다."); alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
} }
} catch { } catch {
setTabStatsStatusInfoMap(new Map());
alert("데이터 조회 중 오류가 발생했습니다."); alert("데이터 조회 중 오류가 발생했습니다.");
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -118,9 +182,16 @@ const SewagePipeRvw = () => {
lazyTabLoggedRef.current.add(tabKey); lazyTabLoggedRef.current.add(tabKey);
if (response?.status === "success" && response.data) { if (response?.status === "success" && response.data) {
const ssi = extractStatsStatusInfo(response.data);
setTabStatsStatusInfoMap((prev) => {
const next = new Map(prev);
next.set(tabKey, ssi ?? {});
return next;
});
const rows = extractReviewRowsFromResponseData(tabKey, response.data);
setRowDataMap((prev) => { setRowDataMap((prev) => {
const next = new Map(prev); const next = new Map(prev);
next.set(tabKey, toGridRows(response.data as object[])); next.set(tabKey, toGridRows(rows));
return next; return next;
}); });
} else { } else {
@@ -137,14 +208,19 @@ const SewagePipeRvw = () => {
<div className="relative"> <div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} /> <SearchForm onSubmit={handleSearch} onReset={handleReset} />
<div className="card !py-0"> {showActionBar && (
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]"> <div className="card !py-0">
<ReviewOpinionButton reviewOpinionFunc={() => { }} /> <div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ApproveButton approveFunc={() => { }} /> {showReviewOpinionButton && (
<ReturnButton returnFunc={() => { }} /> <ReviewOpinionButton reviewOpinionFunc={() => {}} />
<SaveButton saveFunc={() => { }} /> )}
{showReturnButton && <ReturnButton returnFunc={() => {}} />}
{showSubmitButton && (
<SubmitButton submitFunc={() => {}} />
)}
</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">
<div className="relative size-[5rem]"> <div className="relative size-[5rem]">
@@ -1,9 +1,79 @@
import { useState } from "react";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { PublicSewageFacilityColumn } from "@/components/util/column/total/PublicSewageFacilityColumn";
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
import { toGridRows } from "@/components/util/grid/gridFunction";
import { useAlertStore } from "@/shared/lib";
import SearchForm from "@/components/module/SearchForm"; import 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 [isLoading, setIsLoading] = useState<boolean>(false);
const { setMessage: alert } = useAlertStore((state) => state);
const handleSearch = async (values: SearchFormValues) => {
try {
setIsLoading(true);
const isSummaryEnabled = values.isSummary !== 'ALL';
const summaryYearCnt = isSummaryEnabled ? Number(values.isSummary) : undefined;
const param = {
ctpvNm: values.sido === 'ALL' ? '' : values.sido,
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
sewerBzmnCd: values.bsnmCd,
statsYr: values.year,
rowDataYn: values.isDetail,
summaryYn: isSummaryEnabled,
...(isSummaryEnabled ? { summaryYearCnt } : {}),
};
const response = await statisticsTotalStatusApi.getSewagePublicFacillityTotal(param);
if (response.status === 'success') {
setRowData(toGridRows(response.data));
} else {
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
}
} catch (error) {
console.log(error);
alert('데이터 조회 중 오류가 발생했습니다.');
} finally {
setIsLoading(false);
}
};
return ( return (
<> <>
<SearchForm /> <SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
<CellColorInfo />
<DataGrid
columnInfo={PublicSewageFacilityColumn}
rowStartNum={2}
isExcelImport={false}
fixedColumns={2}
rowData={rowData}
setRowData={setRowData}
cellStyleCallback={({ row }) => {
const rowType = String(row.rowType ?? '');
if (rowType === 'DETAIL') return undefined;
if (rowType === 'BIZ_SUM') return 'rg-cell-rowtype-biz-sum';
if (rowType === 'SUMMARY') return 'rg-cell-rowtype-summary';
return 'rg-cell-rowtype-other';
}}
/>
</> </>
); );
}; };
@@ -25,6 +25,7 @@ function buildSewagePipeTotSearchParam(values: SearchFormValues): SewagePipeTota
sewerBzmnCd: values.bsnmCd, sewerBzmnCd: values.bsnmCd,
statsYr: values.year, statsYr: values.year,
rowDataYn: values.isDetail, rowDataYn: values.isDetail,
summaryYn: isSummaryEnabled,
...(isSummaryEnabled ? { summaryYearCnt } : {}), ...(isSummaryEnabled ? { summaryYearCnt } : {}),
}; };
} }
@@ -106,6 +107,15 @@ const SewagePipeTot = () => {
alert(response.message || "데이터 조회 중 오류가 발생했습니다."); alert(response.message || "데이터 조회 중 오류가 발생했습니다.");
return false; return false;
} }
if (tabKey === "ST00020005") {
const response = await statisticsTotalStatusApi.getSewagePipePipeMnhleStatus(param);
if (response.status === "success" && response.data) {
applyRows(toGridRows(response.data as object[]));
return true;
}
alert(response.message || "데이터 조회 중 오류가 발생했습니다.");
return false;
}
return false; return false;
}; };
@@ -120,7 +130,8 @@ const SewagePipeTot = () => {
activeTab === "ST00020001" || activeTab === "ST00020001" ||
activeTab === "ST00020002" || activeTab === "ST00020002" ||
activeTab === "ST00020003" || activeTab === "ST00020003" ||
activeTab === "ST00020004" activeTab === "ST00020004" ||
activeTab === "ST00020005"
) { ) {
ok = await loadSewagePipeTotTabData(activeTab, values, true); ok = await loadSewagePipeTotTabData(activeTab, values, true);
} }
@@ -151,7 +162,8 @@ const SewagePipeTot = () => {
tabKey === "ST00020001" || tabKey === "ST00020001" ||
tabKey === "ST00020002" || tabKey === "ST00020002" ||
tabKey === "ST00020003" || tabKey === "ST00020003" ||
tabKey === "ST00020004" tabKey === "ST00020004" ||
tabKey === "ST00020005"
) { ) {
try { try {
setIsLoading(true); setIsLoading(true);
@@ -114,10 +114,10 @@ export default function HbStatisticsVerification() {
const [verifyType, setVerifyType] = useState<VerifyType>("ALL"); const [verifyType, setVerifyType] = useState<VerifyType>("ALL");
const [formState, setFormState] = useState<VerificationFormState>({ const [formState, setFormState] = useState<VerificationFormState>({
mu: "0", mu: "",
percentile: "0", percentile: "",
a: "0", a: "",
c: "0", c: "",
verificationYear: defaultYear, verificationYear: defaultYear,
}); });
const [selectedTargets, setSelectedTargets] = useState<string[]>([]); const [selectedTargets, setSelectedTargets] = useState<string[]>([]);
@@ -559,11 +559,14 @@ export default function HbStatisticsVerification() {
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Label className="w-32 text-label"> <Label className="w-48 whitespace-nowrap text-label">
μ 중요도 계수(μ)
</Label> </Label>
<Input <Input
type="number" type="number"
min="0"
max="1"
step="0.1"
value={formState.mu} value={formState.mu}
onChange={(event) => onChange={(event) =>
handleFormChange( handleFormChange(
@@ -572,15 +575,19 @@ export default function HbStatisticsVerification() {
) )
} }
className="flex-1" className="flex-1"
placeholder="(0 ~ 1, 0.1 단위)"
/> />
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Label className="w-32 text-label"> <Label className="w-48 text-label">
Percentile 백분위수 기준값(pct)
</Label> </Label>
<Input <Input
type="number" type="number"
min="0"
max="0.5"
step="0.01"
value={formState.percentile} value={formState.percentile}
onChange={(event) => onChange={(event) =>
handleFormChange( handleFormChange(
@@ -589,15 +596,19 @@ export default function HbStatisticsVerification() {
) )
} }
className="flex-1" className="flex-1"
placeholder="(0 ~ 0.5, 0.01 단위)"
/> />
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Label className="w-32 text-label"> <Label className="w-48 text-label">
A 스케일 조정 계수(A)
</Label> </Label>
<Input <Input
type="number" type="number"
min="0"
max="1"
step="0.01"
value={formState.a} value={formState.a}
onChange={(event) => onChange={(event) =>
handleFormChange( handleFormChange(
@@ -606,15 +617,19 @@ export default function HbStatisticsVerification() {
) )
} }
className="flex-1" className="flex-1"
placeholder="(0 ~ 1, 0.01 단위)"
/> />
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Label className="w-32 text-label"> <Label className="w-48 text-label">
C 임계 계수(c)
</Label> </Label>
<Input <Input
type="number" type="number"
min="0"
max="100"
step="1"
value={formState.c} value={formState.c}
onChange={(event) => onChange={(event) =>
handleFormChange( handleFormChange(
@@ -623,6 +638,7 @@ export default function HbStatisticsVerification() {
) )
} }
className="flex-1" className="flex-1"
placeholder="(0 ~ 100, 1 단위)"
/> />
</div> </div>
</div> </div>
@@ -4,7 +4,6 @@ import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Search } from "lucide-react";
import { CodeSelect } from "@/entities/code/ui/code-select"; import { CodeSelect } from "@/entities/code/ui/code-select";
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service"; import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
@@ -368,7 +367,7 @@ export function VerificationTargetDialog({
type="number" type="number"
min="0" min="0"
max="0.5" max="0.5"
step="0.1" step="0.01"
value={ value={
field.value ?? field.value ??
"" ""
@@ -385,7 +384,7 @@ export function VerificationTargetDialog({
: null, : null,
) )
} }
placeholder="(0 ~ 0.5, 0.1 단위)" placeholder="(0 ~ 0.5, 0.01 단위)"
/> />
</div> </div>
)} )}
@@ -369,6 +369,10 @@ export type SplitDataGridProps = {
leftOnRowClick?: (rowData: Record<string, string | number>) => void; leftOnRowClick?: (rowData: Record<string, string | number>) => void;
/** 좌측 분할 그리드 셀 스타일 콜백 */ /** 좌측 분할 그리드 셀 스타일 콜백 */
leftCellStyleCallback?: CellStyleCallback; leftCellStyleCallback?: CellStyleCallback;
/** 우측 분할 그리드 행 체크 변경 */
rightOnItemChecked?: (itemIndex: number, checked: boolean) => void;
/** 우측 분할 그리드 헤더 전체 체크 변경 */
rightOnItemAllChecked?: (checked: boolean) => void;
/** 전체 정렬 기능 on/off (기본값: false) */ /** 전체 정렬 기능 on/off (기본값: false) */
enableSort?: boolean; enableSort?: boolean;
}; };
@@ -1,3 +1,6 @@
/** 통계 분류: 전체 / 입력 / 검증 */
export type SearchFormClsfCd = "ALL" | "INPT" | "VRFC";
export type SearchFormValues = { export type SearchFormValues = {
sido?: string; sido?: string;
gugun?: string; gugun?: string;
@@ -7,6 +10,8 @@ export type SearchFormValues = {
bsnmCd?: string; bsnmCd?: string;
isDetail?: boolean; isDetail?: boolean;
isSummary?: string; isSummary?: string;
/** 분류: 전체(ALL), 입력(INPT), 검증(VRFC) */
clsfCd?: SearchFormClsfCd;
}; };
export type SearchFormProps = { export type SearchFormProps = {
@@ -16,6 +21,8 @@ export type SearchFormProps = {
year?: boolean; year?: boolean;
order?: boolean; order?: boolean;
fctName?: boolean; fctName?: boolean;
/** 분류(입력/검증) 셀렉트 표시 */
clsf?: boolean;
userMenual?: boolean; userMenual?: boolean;
isDetail?: boolean; isDetail?: boolean;
isSummary?: boolean; isSummary?: boolean;
@@ -33,6 +40,7 @@ export const defaultSearchFormProps: Required<
year: true, year: true,
order: false, order: false,
fctName: false, fctName: false,
clsf: false,
userMenual: false, userMenual: false,
isDetail: false, isDetail: false,
isSummary: false, isSummary: false,
@@ -46,6 +46,7 @@ export const MainFacilityTargetColumn = {
{ name: 'fcltSn', fieldName: 'fcltSn', header: { text: '처리시설일련번호' }, visible: false, editable: false, valueType: ValueType.TEXT, styleName: 'rg-cell-readonly' }, { name: 'fcltSn', fieldName: 'fcltSn', header: { text: '처리시설일련번호' }, visible: false, editable: false, valueType: ValueType.TEXT, styleName: 'rg-cell-readonly' },
{ name: 'swtrmtFcltCd', fieldName: 'swtrmtFcltCd', header: { text: '하수처리시설코드' }, visible: false, editable: false, styleName: 'rg-cell-readonly' }, { name: 'swtrmtFcltCd', fieldName: 'swtrmtFcltCd', header: { text: '하수처리시설코드' }, visible: false, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false, editable: false, styleName: 'rg-cell-readonly' }, { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'mainFcltSn', fieldName: 'mainFcltSn', header: { text: '연결된중심시설일련번호' }, visible: false, editable: false, styleName: 'rg-cell-readonly' }, { name: 'mainFcltSn', fieldName: 'mainFcltSn', header: { text: '연결된중심시설일련번호' }, visible: false, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'isMainYn', fieldName: 'isMainYn', header: { text: '중심처리시설여부' }, visible: false, editable: false, styleName: 'rg-cell-readonly' }, { name: 'isMainYn', fieldName: 'isMainYn', header: { text: '중심처리시설여부' }, visible: false, editable: false, styleName: 'rg-cell-readonly' },
@@ -56,6 +57,7 @@ export const MainFacilityTargetColumn = {
'mappedYn', 'mappedYn',
'fcltSn', 'fcltSn',
'swtrmtFcltCd', 'swtrmtFcltCd',
'admdstCd',
'admdstNm', 'admdstNm',
'mainFcltSn', 'mainFcltSn',
'isMainYn', 'isMainYn',
@@ -21,9 +21,9 @@ export const PublicSewageFactColumn = {
{ name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '시설용량(500m³/일 이상/미만)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-total' }, { name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '시설용량(500m³/일 이상/미만)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-total' },
// (m³/일) // (m³/일)
{ name: 'prcsFcltPhysCpct', fieldName: 'prcsFcltPhysCpct', header: { text: '물리적' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, { name: 'prcsFcltPhysCpct', fieldName: 'prcsFcltPhysCpct', header: { text: '물리적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'prcsFcltBoilCpct', fieldName: 'prcsFcltBoilCpct', header: { text: '생물학적' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, { name: 'prcsFcltBoilCpct', fieldName: 'prcsFcltBoilCpct', header: { text: '생물학적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'prcsFcltAltdCpct', fieldName: 'prcsFcltAltdCpct', header: { text: '고도' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, { name: 'prcsFcltAltdCpct', fieldName: 'prcsFcltAltdCpct', header: { text: '고도' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 유입하수량 // 유입하수량
{ name: 'inflwSwageQnt', fieldName: 'inflwSwageQnt', header: { text: '유입하수량(500m³/일 이상/미만)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' }, { name: 'inflwSwageQnt', fieldName: 'inflwSwageQnt', header: { text: '유입하수량(500m³/일 이상/미만)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' },
@@ -0,0 +1,298 @@
import { ValueType } from "realgrid";
/**
* 하수도 통계 관리 > 06. 공공하수처리시설
* 단일 Tab
*/
export const PublicSewageFacilityColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, editable: false },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false },
// 시설명 (500㎥/일 이상/미만)
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '시설명\n(500㎥/일\n이상/미만)' }, editable: false },
// 소재지
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '소재지' }, editable: false },
// 시설용량 (㎥/일)
{
name: 'prcsFcltCpct',
fieldName: 'prcsFcltCpct',
header: { text: ' 시설용량 500㎥/일\n이상/미만' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0',
},
{
name: 'prcsFcltPhysCpct',
fieldName: 'prcsFcltPhysCpct',
header: { text: '물리적' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
{
name: 'prcsFcltBoilCpct',
fieldName: 'prcsFcltBoilCpct',
header: { text: '생물학적' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
{
name: 'prcsFcltAltdCpct',
fieldName: 'prcsFcltAltdCpct',
header: { text: '고도' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
// 유입하수량 (㎥/일)
{
name: 'inflwSwageQnt',
fieldName: 'inflwSwageQnt',
header: { text: '유입하수량 500㎥/일\n이상/미만' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
{
name: 'inflwSwagePhysQnt',
fieldName: 'inflwSwagePhysQnt',
header: { text: '물리적' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
{
name: 'inflwSwageBoilQnt',
fieldName: 'inflwSwageBoilQnt',
header: { text: '생물학적' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
{
name: 'inflwSwageAltdQnt',
fieldName: 'inflwSwageAltdQnt',
header: { text: '고도' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
// 방류량 (㎥/일)
{
name: 'prcsQnt',
fieldName: 'prcsQnt',
header: { text: '방류량 500㎥/일\n이상/미만' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
{
name: 'prcsPhysCpct',
fieldName: 'prcsPhysCpct',
header: { text: '물리적' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
{
name: 'prcsBoilCpct',
fieldName: 'prcsBoilCpct',
header: { text: '생물학적' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
{
name: 'prcsAltdCpct',
fieldName: 'prcsAltdCpct',
header: { text: '고도' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
},
// 수질 정보 - 유입/방류 교차
{ name: 'inflwBodVl', fieldName: 'inflwBodVl', header: { text: '유입\nBOD' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'dschgBodVl', fieldName: 'dschgBodVl', header: { text: '방류\nBOD' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'inflwTocQnt', fieldName: 'inflwTocQnt', header: { text: '유입\nTOC' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'dschgTocQnt', fieldName: 'dschgTocQnt', header: { text: '방류\nTOC' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'inflwSsQnt', fieldName: 'inflwSsQnt', header: { text: '유입\nSS' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'dschgSsQnt', fieldName: 'dschgSsQnt', header: { text: '방류\nSS' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'inflwTnVl', fieldName: 'inflwTnVl', header: { text: '유입\nT-N' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'dschgTnVl', fieldName: 'dschgTnVl', header: { text: '방류\nT-N' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'inflwTpVl', fieldName: 'inflwTpVl', header: { text: '유입\nT-P' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'dschgTpVl', fieldName: 'dschgTpVl', header: { text: '방류\nT-P' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'inflwGrssClfgVl', fieldName: 'inflwGrssClfgVl', header: { text: '유입\n총대장균군수' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'dschgGrssClfgVl', fieldName: 'dschgGrssClfgVl', header: { text: '방류\n총대장균군수' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
// 처리 효율 및 방법
{
name: 'prcsRt',
fieldName: 'prcsRt',
header: { text: '처리\n효율(%)' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.0',
},
{
name: 'prcsLodQnt',
fieldName: 'prcsLodQnt',
header: { text: '처리부하량\n(kg BOD/D)' },
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0',
},
{ name: 'prcsMthdExpln', fieldName: 'prcsMthdExpln', header: { text: '처리\n방법' }, editable: false },
// 연계처리량 (㎥/일)
{ name: 'wholHmnQnt', fieldName: 'wholHmnQnt', header: { text: '분뇨' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'wholLvstQnt', fieldName: 'wholLvstQnt', header: { text: '축산' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'wholEfntQnt', fieldName: 'wholEfntQnt', header: { text: '침출수' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'wholEtcQnt', fieldName: 'wholEtcQnt', header: { text: '기타' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 부가 정보
{ name: 'telNo', fieldName: 'telNo', header: { text: '전화번호' }, editable: false },
{ name: 'cmptnStrtYmd', fieldName: 'cmptnStrtYmd', header: { text: '준공일' }, editable: false },
{ name: 'oprtngStrtYmd', fieldName: 'oprtngStrtYmd', header: { text: '가동개시일' }, editable: false },
{ name: 'wholBizVl', fieldName: 'wholBizVl', header: { text: '사업비\n(백만원)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'eflntDsnfMthdExpln', fieldName: 'eflntDsnfMthdExpln', header: { text: '방류수\n소독방법' }, editable: false },
// 방류수역 정보
{ name: 'wtsysNm', fieldName: 'wtsysNm', header: { text: '수계' }, editable: false },
{ name: 'trbtrNm', fieldName: 'trbtrNm', header: { text: '지류' }, editable: false },
{ name: 'dtlUnitZoneCn', fieldName: 'dtlUnitZoneCn', header: { text: '세부단위구역' }, editable: false },
{ name: 'rgnSeNm', fieldName: 'rgnSeNm', header: { text: '지역구분' }, editable: false },
// 중권역
{ name: 'msirbCn', fieldName: 'msirbCn', header: { text: '명칭' }, editable: false },
{ name: 'goalWqNo', fieldName: 'goalWqNo', header: { text: '목표수질\n(BOD)' }, editable: false },
// 에너지
{ name: 'engySrlneRt', fieldName: 'engySrlneRt', header: { text: '에너지\n자립율(%)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'engyItslfPrdctnQnt', fieldName: 'engyItslfPrdctnQnt', header: { text: '에너지\n자체생산량\n(TOE)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'anlEngyWholUseQnt', fieldName: 'anlEngyWholUseQnt', header: { text: '계(TOE)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'anlElpwrUseQnt', fieldName: 'anlElpwrUseQnt', header: { text: '총전력사용량\n(kWh)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'itslfEngyUseQnt', fieldName: 'itslfEngyUseQnt', header: { text: '자체에너지\n사용량(TOE)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'swtrmtEmsnQnt', fieldName: 'swtrmtEmsnQnt', header: { text: '하수처리량당\nCO2배출량\n(㎏CO2/㎥)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
// 운영주체
{ name: 'operMnbdCn', fieldName: 'operMnbdCn', header: { text: '운영주체\n(자체/공기업/\n민간대행)' }, editable: false },
// 위탁현황
{ name: 'prxyBzentyNmCn', fieldName: 'prxyBzentyNmCn', header: { text: '위탁\n업체명' }, editable: false },
{ name: 'cnsgnCstVl', fieldName: 'cnsgnCstVl', header: { text: '위탁비용\n(백만원/년)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'prxyCtrtCn', fieldName: 'prxyCtrtCn', header: { text: '위탁계약기간\n(yyyy-mm-dd\n~\nyyyy-mm-dd)' }, editable: false },
{ name: 'intgPrcsFcltCn', fieldName: 'intgPrcsFcltCn', header: { text: '중심처리시설' }, editable: false },
{ name: 'wholNope', fieldName: 'wholNope', header: { text: '직원총수\n(명)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sftyAcdntEvntcsCnt', fieldName: 'sftyAcdntEvntcsCnt', header: { text: '안전사고건수' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// hidden
{ name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false },
{ name: 'rowType', fieldName: 'rowType', header: { text: '데이터구분' }, visible: false },
{ name: 'statsYr', fieldName: 'statsYr', header: { text: '통계연도' }, visible: false },
].map((col) => ({ ...col, editable: false })),
columnLayout: [
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '행정구역' },
items: ['ctpvNm', 'sggNm', 'admdstNm'],
},
'swtrmtFcltNm',
'fcltPstnCn',
'prcsFcltCpct',
{
name: 'groupCapacity',
hideChildHeaders: false,
header: { text: '(㎥/일)' },
items: [ 'prcsFcltPhysCpct', 'prcsFcltBoilCpct', 'prcsFcltAltdCpct'],
},
'inflwSwageQnt',
{
name: 'groupInflw',
hideChildHeaders: false,
header: { text: '(㎥/일)' },
items: [ 'inflwSwagePhysQnt', 'inflwSwageBoilQnt', 'inflwSwageAltdQnt'],
},
'prcsQnt',
{
name: 'groupPrcs',
hideChildHeaders: false,
header: { text: '(㎥/일)' },
items: [ 'prcsPhysCpct', 'prcsBoilCpct', 'prcsAltdCpct'],
},
'inflwBodVl',
'dschgBodVl',
'inflwTocQnt',
'dschgTocQnt',
'inflwSsQnt',
'dschgSsQnt',
'inflwTnVl',
'dschgTnVl',
'inflwTpVl',
'dschgTpVl',
'inflwGrssClfgVl',
'dschgGrssClfgVl',
'prcsRt',
'prcsLodQnt',
'prcsMthdExpln',
{
name: 'groupLinkPrcs',
hideChildHeaders: false,
header: { text: '연계처리량(㎥/일)\n(500㎥/일 이상/미만)' },
items: ['wholHmnQnt', 'wholLvstQnt', 'wholEfntQnt', 'wholEtcQnt'],
},
'telNo',
'cmptnStrtYmd',
'oprtngStrtYmd',
'wholBizVl',
'eflntDsnfMthdExpln',
{
name: 'groupDschgWater',
hideChildHeaders: false,
header: { text: '방류수역' },
items: [
'wtsysNm',
'trbtrNm',
'dtlUnitZoneCn',
'rgnSeNm',
{
name: 'groupMsirbCn',
hideChildHeaders: false,
header: { text: '중권역' },
items: ['msirbCn', 'goalWqNo'],
},
],
},
'engySrlneRt',
'engyItslfPrdctnQnt',
{
name: 'groupEngyUse',
hideChildHeaders: false,
header: { text: '연간 총 에너지 사용량(TOE)' },
items: ['anlEngyWholUseQnt', 'anlElpwrUseQnt', 'itslfEngyUseQnt'],
},
'swtrmtEmsnQnt',
'operMnbdCn',
{
name: 'groupCnsgn',
hideChildHeaders: false,
header: { text: '위탁현황' },
items: ['prxyBzentyNmCn', 'cnsgnCstVl', 'prxyCtrtCn'],
},
'intgPrcsFcltCn',
'wholNope',
'sftyAcdntEvntcsCnt',
'admdstCd',
'rowType',
'statsYr',
],
};
@@ -706,7 +706,7 @@ const rawSewagePipeTotList = [
tabKey: "ST00020004", tabKey: "ST00020004",
tabLabel: "관경별 현황", tabLabel: "관경별 현황",
rowStartNum: 2, rowStartNum: 2,
fixedColumns: 3, fixedColumns: 1,
columns: [ columns: [
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false }, { name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false }, { name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
@@ -794,9 +794,238 @@ const rawSewagePipeTotList = [
"etcPdmtPrlg", "etcPdmtPrlg",
], ],
}, },
{
tabKey: "ST00020005",
tabLabel: "맨홀 현황",
rowStartNum: 2,
fixedColumns: 2,
columns: [
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역명" }, editable: false },
{ name: "emphsMngSeNm", fieldName: "emphsMngSeNm", header: { text: "집중강우\n중점관리\n구역구분" }, editable: false },
{ name: "totWholMnhlCnt", fieldName: "totWholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep1MnhlCnt", fieldName: "totStep1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep2MnhlCnt", fieldName: "totStep2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep3MnhlCnt", fieldName: "totStep3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep4MnhlCnt", fieldName: "totStep4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep5MnhlCnt", fieldName: "totStep5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2015년 이전 (yy14) ──────────────────────────────────
{ name: "yy14WholMnhlCnt", fieldName: "yy14WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step1MnhlCnt", fieldName: "yy14Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step2MnhlCnt", fieldName: "yy14Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step3MnhlCnt", fieldName: "yy14Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step4MnhlCnt", fieldName: "yy14Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step5MnhlCnt", fieldName: "yy14Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2015년 (yy15) ───────────────────────────────────────
{ name: "yy15WholMnhlCnt", fieldName: "yy15WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step1MnhlCnt", fieldName: "yy15Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step2MnhlCnt", fieldName: "yy15Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step3MnhlCnt", fieldName: "yy15Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step4MnhlCnt", fieldName: "yy15Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step5MnhlCnt", fieldName: "yy15Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2016년 (yy16) ───────────────────────────────────────
{ name: "yy16WholMnhlCnt", fieldName: "yy16WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step1MnhlCnt", fieldName: "yy16Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step2MnhlCnt", fieldName: "yy16Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step3MnhlCnt", fieldName: "yy16Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step4MnhlCnt", fieldName: "yy16Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step5MnhlCnt", fieldName: "yy16Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2017년 (yy17) ───────────────────────────────────────
{ name: "yy17WholMnhlCnt", fieldName: "yy17WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step1MnhlCnt", fieldName: "yy17Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step2MnhlCnt", fieldName: "yy17Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step3MnhlCnt", fieldName: "yy17Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step4MnhlCnt", fieldName: "yy17Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step5MnhlCnt", fieldName: "yy17Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2018년 (yy18) ───────────────────────────────────────
{ name: "yy18WholMnhlCnt", fieldName: "yy18WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step1MnhlCnt", fieldName: "yy18Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step2MnhlCnt", fieldName: "yy18Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step3MnhlCnt", fieldName: "yy18Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step4MnhlCnt", fieldName: "yy18Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step5MnhlCnt", fieldName: "yy18Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2019년 (yy19) ───────────────────────────────────────
{ name: "yy19WholMnhlCnt", fieldName: "yy19WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step1MnhlCnt", fieldName: "yy19Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step2MnhlCnt", fieldName: "yy19Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step3MnhlCnt", fieldName: "yy19Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step4MnhlCnt", fieldName: "yy19Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step5MnhlCnt", fieldName: "yy19Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2020년 (yy20) ───────────────────────────────────────
{ name: "yy20WholMnhlCnt", fieldName: "yy20WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step1MnhlCnt", fieldName: "yy20Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step2MnhlCnt", fieldName: "yy20Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step3MnhlCnt", fieldName: "yy20Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step4MnhlCnt", fieldName: "yy20Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step5MnhlCnt", fieldName: "yy20Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2021년 (yy21) ───────────────────────────────────────
{ name: "yy21WholMnhlCnt", fieldName: "yy21WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step1MnhlCnt", fieldName: "yy21Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step2MnhlCnt", fieldName: "yy21Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step3MnhlCnt", fieldName: "yy21Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step4MnhlCnt", fieldName: "yy21Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step5MnhlCnt", fieldName: "yy21Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2022년 (yy22) ───────────────────────────────────────
{ name: "yy22WholMnhlCnt", fieldName: "yy22WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step1MnhlCnt", fieldName: "yy22Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step2MnhlCnt", fieldName: "yy22Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step3MnhlCnt", fieldName: "yy22Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step4MnhlCnt", fieldName: "yy22Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step5MnhlCnt", fieldName: "yy22Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2023년 (yy23) ───────────────────────────────────────
{ name: "yy23WholMnhlCnt", fieldName: "yy23WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step1MnhlCnt", fieldName: "yy23Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step2MnhlCnt", fieldName: "yy23Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step3MnhlCnt", fieldName: "yy23Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step4MnhlCnt", fieldName: "yy23Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step5MnhlCnt", fieldName: "yy23Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2024년 (yy24) ───────────────────────────────────────
{ name: "yy24WholMnhlCnt", fieldName: "yy24WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step1MnhlCnt", fieldName: "yy24Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step2MnhlCnt", fieldName: "yy24Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step3MnhlCnt", fieldName: "yy24Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step4MnhlCnt", fieldName: "yy24Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step5MnhlCnt", fieldName: "yy24Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2025년 (yy25) ───────────────────────────────────────
{ name: "yy25WholMnhlCnt", fieldName: "yy25WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step1MnhlCnt", fieldName: "yy25Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step2MnhlCnt", fieldName: "yy25Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step3MnhlCnt", fieldName: "yy25Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step4MnhlCnt", fieldName: "yy25Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step5MnhlCnt", fieldName: "yy25Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" }, editable: false },
],
columnLayout: [
{ name: "addrGroup", header: { text: "행정구역" }, items: ["ctpvNm", "sggNm"] },
"emphsMngSeNm",
{
name: "totGroup",
header: { text: "연도별총계" },
items: [
{ name: "totMtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["totWholMnhlCnt", "totStep1MnhlCnt", "totStep2MnhlCnt", "totStep3MnhlCnt", "totStep4MnhlCnt"] },
"totStep5MnhlCnt",
],
},
{
name: "yy14Group",
header: { text: "2015년 이전" }, // ← _14Y
items: [
{ name: "yy14MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy14WholMnhlCnt", "yy14Step1MnhlCnt", "yy14Step2MnhlCnt", "yy14Step3MnhlCnt", "yy14Step4MnhlCnt"] },
"yy14Step5MnhlCnt",
],
},
{
name: "yy15Group",
header: { text: "2015년" }, // ← _15Y
items: [
{ name: "yy15MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy15WholMnhlCnt", "yy15Step1MnhlCnt", "yy15Step2MnhlCnt", "yy15Step3MnhlCnt", "yy15Step4MnhlCnt"] },
"yy15Step5MnhlCnt",
],
},
{
name: "yy16Group",
header: { text: "2016년" }, // ← _16Y
items: [
{ name: "yy16MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy16WholMnhlCnt", "yy16Step1MnhlCnt", "yy16Step2MnhlCnt", "yy16Step3MnhlCnt", "yy16Step4MnhlCnt"] },
"yy16Step5MnhlCnt",
],
},
{
name: "yy17Group",
header: { text: "2017년" },
items: [
{ name: "yy17MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy17WholMnhlCnt", "yy17Step1MnhlCnt", "yy17Step2MnhlCnt", "yy17Step3MnhlCnt", "yy17Step4MnhlCnt"] },
"yy17Step5MnhlCnt",
],
},
{
name: "yy18Group",
header: { text: "2018년" },
items: [
{ name: "yy18MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy18WholMnhlCnt", "yy18Step1MnhlCnt", "yy18Step2MnhlCnt", "yy18Step3MnhlCnt", "yy18Step4MnhlCnt"] },
"yy18Step5MnhlCnt",
],
},
{
name: "yy19Group",
header: { text: "2019년" },
items: [
{ name: "yy19MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy19WholMnhlCnt", "yy19Step1MnhlCnt", "yy19Step2MnhlCnt", "yy19Step3MnhlCnt", "yy19Step4MnhlCnt"] },
"yy19Step5MnhlCnt",
],
},
{
name: "yy20Group",
header: { text: "2020년" },
items: [
{ name: "yy20MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy20WholMnhlCnt", "yy20Step1MnhlCnt", "yy20Step2MnhlCnt", "yy20Step3MnhlCnt", "yy20Step4MnhlCnt"] },
"yy20Step5MnhlCnt",
],
},
{
name: "yy21Group",
header: { text: "2021년" },
items: [
{ name: "yy21MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy21WholMnhlCnt", "yy21Step1MnhlCnt", "yy21Step2MnhlCnt", "yy21Step3MnhlCnt", "yy21Step4MnhlCnt"] },
"yy21Step5MnhlCnt",
],
},
{
name: "yy22Group",
header: { text: "2022년" },
items: [
{ name: "yy22MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy22WholMnhlCnt", "yy22Step1MnhlCnt", "yy22Step2MnhlCnt", "yy22Step3MnhlCnt", "yy22Step4MnhlCnt"] },
"yy22Step5MnhlCnt",
],
},
{
name: "yy23Group",
header: { text: "2023년" },
items: [
{ name: "yy23MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy23WholMnhlCnt", "yy23Step1MnhlCnt", "yy23Step2MnhlCnt", "yy23Step3MnhlCnt", "yy23Step4MnhlCnt"] },
"yy23Step5MnhlCnt",
],
},
{
name: "yy24Group",
header: { text: "2024년" },
items: [
{ name: "yy24MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy24WholMnhlCnt", "yy24Step1MnhlCnt", "yy24Step2MnhlCnt", "yy24Step3MnhlCnt", "yy24Step4MnhlCnt"] },
"yy24Step5MnhlCnt",
],
},
{
name: "yy25Group",
header: { text: "2025년" },
items: [
{ name: "yy25MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy25WholMnhlCnt", "yy25Step1MnhlCnt", "yy25Step2MnhlCnt", "yy25Step3MnhlCnt", "yy25Step4MnhlCnt"] },
"yy25Step5MnhlCnt",
],
},
"rmrk",
],
},
]; ];
export const SewagePipeColumnInfoListTot = rawSewagePipeTotList.map((tab) => ({ export const SewagePipeColumnInfoListTot = rawSewagePipeTotList.map((tab) => ({
...tab, ...tab,
columns: tab.columns.map((col) => ({ ...col, editable: false as const })), columns: tab.columns.map((col) => ({ ...col, editable: false as const })),
})); }));
@@ -28,7 +28,7 @@ export class StatisticsInputSewSupplyRateApiService implements StatisticsInputSe
} }
public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveResponse> { public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveResponse> {
const response = await stClient.post<SewerageSupplyRateSaveResponse>( const response = await stClient.put<SewerageSupplyRateSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate/save`, `${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate/save`,
params, params,
); );
@@ -36,7 +36,7 @@ export class StatisticsInputSewSupplyRateApiService implements StatisticsInputSe
} }
public async submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSubmitResponse> { public async submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSubmitResponse> {
const response = await stClient.post<SewerageSupplyRateSubmitResponse>( const response = await stClient.put<SewerageSupplyRateSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate/submit`, `${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate/submit`,
params, params,
); );
@@ -8,6 +8,8 @@ import type {
PublicSewageFacilitySaveResponse, PublicSewageFacilitySaveResponse,
PublicSewageFacilitySubmitRequest, PublicSewageFacilitySubmitRequest,
PublicSewageFacilitySubmitResponse, PublicSewageFacilitySubmitResponse,
PublicSewageMainFacilityMappingSaveRequest,
PublicSewageMainFacilityMappingSaveResponse,
PublicSewageMainFacilityResponse, PublicSewageMainFacilityResponse,
PublicSewageMappingTargetsRequestParams, PublicSewageMappingTargetsRequestParams,
PublicSewageMappingTargetsResponse, PublicSewageMappingTargetsResponse,
@@ -41,10 +43,20 @@ export class StatisticsInputPublicSewageFacilityApiService implements Statistics
return response.data; return response.data;
} }
public async saveMainFacilityMapping(
param: PublicSewageMainFacilityMappingSaveRequest,
): Promise<PublicSewageMainFacilityMappingSaveResponse> {
const response = await stClient.put<PublicSewageMainFacilityMappingSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/main-facility/save`,
param,
);
return response.data;
}
public async savePublicSewageFacility( public async savePublicSewageFacility(
param: PublicSewageFacilitySaveRequest, param: PublicSewageFacilitySaveRequest,
): Promise<PublicSewageFacilitySaveResponse> { ): Promise<PublicSewageFacilitySaveResponse> {
const response = await stClient.post<PublicSewageFacilitySaveResponse>( const response = await stClient.put<PublicSewageFacilitySaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/facility/save`, `${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/facility/save`,
param, param,
); );
@@ -54,7 +66,7 @@ export class StatisticsInputPublicSewageFacilityApiService implements Statistics
public async submitPublicSewageFacility( public async submitPublicSewageFacility(
param: PublicSewageFacilitySubmitRequest, param: PublicSewageFacilitySubmitRequest,
): Promise<PublicSewageFacilitySubmitResponse> { ): Promise<PublicSewageFacilitySubmitResponse> {
const response = await stClient.post<PublicSewageFacilitySubmitResponse>( const response = await stClient.put<PublicSewageFacilitySubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/facility/submit`, `${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/facility/submit`,
param, param,
); );
@@ -101,7 +101,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async saveFactConnection(param: SeweragePipeFactConnectionSaveRequest): Promise<SeweragePipeFactConnectionSaveResponse> { public async saveFactConnection(param: SeweragePipeFactConnectionSaveRequest): Promise<SeweragePipeFactConnectionSaveResponse> {
const response = await stClient.post<SeweragePipeFactConnectionSaveResponse>( const response = await stClient.put<SeweragePipeFactConnectionSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/facility-connection-status/popup/save`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/facility-connection-status/popup/save`,
param, param,
); );
@@ -109,7 +109,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async saveRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSaveRequest): Promise<SeweragePipeInputRegionalFactStatusSaveResponse> { public async saveRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSaveRequest): Promise<SeweragePipeInputRegionalFactStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputRegionalFactStatusSaveResponse>( const response = await stClient.put<SeweragePipeInputRegionalFactStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/regional-facility-status/save`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/regional-facility-status/save`,
param, param,
); );
@@ -117,7 +117,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async submitRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSubmitRequest): Promise<SeweragePipeInputRegionalFactStatusSubmitResponse> { public async submitRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSubmitRequest): Promise<SeweragePipeInputRegionalFactStatusSubmitResponse> {
const response = await stClient.post<SeweragePipeInputRegionalFactStatusSubmitResponse>( const response = await stClient.put<SeweragePipeInputRegionalFactStatusSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/regional-facility-status/submit`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/regional-facility-status/submit`,
param, param,
); );
@@ -125,7 +125,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async saveFactInstallStatus(param: SeweragePipeInputFactInstallStatusSaveRequest): Promise<SeweragePipeInputFactInstallStatusSaveResponse> { public async saveFactInstallStatus(param: SeweragePipeInputFactInstallStatusSaveRequest): Promise<SeweragePipeInputFactInstallStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputFactInstallStatusSaveResponse>( const response = await stClient.put<SeweragePipeInputFactInstallStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/facility-install-status/save`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/facility-install-status/save`,
param, param,
); );
@@ -133,7 +133,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async submitFactInstallStatus(param: SeweragePipeInputFactInstallStatusSubmitRequest): Promise<SeweragePipeInputFactInstallStatusSubmitResponse> { public async submitFactInstallStatus(param: SeweragePipeInputFactInstallStatusSubmitRequest): Promise<SeweragePipeInputFactInstallStatusSubmitResponse> {
const response = await stClient.post<SeweragePipeInputFactInstallStatusSubmitResponse>( const response = await stClient.put<SeweragePipeInputFactInstallStatusSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/facility-install-status/submit`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/facility-install-status/submit`,
param, param,
); );
@@ -141,7 +141,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async savePipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSaveRequest): Promise<SeweragePipeInputPipeTypeStatusSaveResponse> { public async savePipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSaveRequest): Promise<SeweragePipeInputPipeTypeStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputPipeTypeStatusSaveResponse>( const response = await stClient.put<SeweragePipeInputPipeTypeStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-type-status/save`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-type-status/save`,
param, param,
); );
@@ -149,7 +149,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async submitPipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse> { public async submitPipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse> {
const response = await stClient.post<SeweragePipeInputSubmitResponse>( const response = await stClient.put<SeweragePipeInputSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-type-status/submit`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-type-status/submit`,
param, param,
); );
@@ -157,7 +157,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async savePipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSaveRequest): Promise<SeweragePipeInputPipeDiameterStatusSaveResponse> { public async savePipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSaveRequest): Promise<SeweragePipeInputPipeDiameterStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputPipeDiameterStatusSaveResponse>( const response = await stClient.put<SeweragePipeInputPipeDiameterStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-diameter-status/save`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-diameter-status/save`,
param, param,
); );
@@ -165,7 +165,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async submitPipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse> { public async submitPipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse> {
const response = await stClient.post<SeweragePipeInputSubmitResponse>( const response = await stClient.put<SeweragePipeInputSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-diameter-status/submit`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-diameter-status/submit`,
param, param,
); );
@@ -173,7 +173,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async saveManholeStatus(param: SeweragePipeInputManholeStatusSaveRequest): Promise<SeweragePipeInputManholeStatusSaveResponse> { public async saveManholeStatus(param: SeweragePipeInputManholeStatusSaveRequest): Promise<SeweragePipeInputManholeStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputManholeStatusSaveResponse>( const response = await stClient.put<SeweragePipeInputManholeStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/manhole-status/save`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/manhole-status/save`,
param, param,
); );
@@ -181,7 +181,7 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
} }
public async submitManholeStatus(param: SeweragePipeInputManholeStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse> { public async submitManholeStatus(param: SeweragePipeInputManholeStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse> {
const response = await stClient.post<SeweragePipeInputSubmitResponse>( const response = await stClient.put<SeweragePipeInputSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/manhole-status/submit`, `${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/manhole-status/submit`,
param, param,
); );
@@ -3,6 +3,8 @@ import { ENDPOINT } from "@/shared/config";
import type { PageableList } from "@/shared/model"; import type { PageableList } from "@/shared/model";
import type { import type {
BusinessSendRequest,
BusinessSendResponse,
NtftalkTemplate, NtftalkTemplate,
TemplateVariablesResponse, TemplateVariablesResponse,
} from "../../model/notice-talk/notice-talk-sending-interface"; } from "../../model/notice-talk/notice-talk-sending-interface";
@@ -38,6 +40,16 @@ export class NoticeTalkSendingApiService {
); );
return response.data; return response.data;
} }
async sendBusiness(
payload: BusinessSendRequest,
): Promise<BusinessSendResponse> {
const response = await client.post<BusinessSendResponse>(
`${ENDPOINT.NTFTALK_SERVICE.ATA_SEND}/business`,
payload,
);
return response.data;
}
} }
export const noticeTalkSendingApi = new NoticeTalkSendingApiService(); export const noticeTalkSendingApi = new NoticeTalkSendingApiService();
@@ -8,26 +8,32 @@ import {
type getReviewOpinionDetailRequest, type getReviewOpinionDetailRequest,
type getReviewOpinionDetailResponse, type getReviewOpinionDetailResponse,
type ImrRequest, type ImrRequest,
type ImrResponse,
type ImrStatusResponse,
type ReviewOpinionSaveRequest, type ReviewOpinionSaveRequest,
type ReviewOpinionSaveResponse, type ReviewOpinionSaveResponse,
type ReviewReturnSaveRequest, type ReviewReturnSaveRequest,
type ReviewReturnSaveResponse, type ReviewReturnSaveResponse,
type ReviewSubmitRequest, type ReviewSubmitRequest,
type ReviewSubmitResponse, type ReviewSubmitResponse,
type VerificationResultListRequest,
type VerificationResultListResponse,
type SearchCondition, type SearchCondition,
type Sgg, type Sgg,
type StatisticsLogDetailResponse, type StatisticsLogDetailResponse,
type StatisticsLogRequest, type StatisticsLogRequest,
type StatisticsLogResponse, type StatisticsLogResponse,
type StatisticsService, type StatisticsService,
type StatsInputPeriodDeleteApiResponse,
type StatsInputPeriodDeleteRequest,
type StatsInputPeriodListApiResponse,
type StatsInputPeriodListRequest,
type StatsInputPeriodSaveApiResponse,
type StatsInputPeriodSaveRequest,
type SubmitExceptionRequest, type SubmitExceptionRequest,
type SubmitExceptionResponse, type SubmitExceptionResponse,
type ImrResponse,
type ImrStatusResponse,
type SubmitVerifyExceptionRequest, type SubmitVerifyExceptionRequest,
type SubmitVerifyExceptionResponse, type SubmitVerifyExceptionResponse,
type VerificationResultListRequest,
type VerificationResultListResponse,
} from "../model/statistics-interface"; } from "../model/statistics-interface";
export class StatisticsApiService implements StatisticsService { export class StatisticsApiService implements StatisticsService {
@@ -63,6 +69,39 @@ export class StatisticsApiService implements StatisticsService {
return response.data; return response.data;
} }
/** 통계 입력 기간(연도·분류별) 목록 조회 — 경로는 백엔드 스펙에 맞게 수정 */
public async getStatsInputPeriodList(
params?: StatsInputPeriodListRequest,
): Promise<StatsInputPeriodListApiResponse> {
const response = await stClient.get<StatsInputPeriodListApiResponse>(
`${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info/list`,
{ params },
);
return response.data;
}
/** 통계 입력 기간 저장 — 경로는 백엔드 스펙에 맞게 수정 */
public async saveStatsInputPeriod(
param: StatsInputPeriodSaveRequest,
): Promise<StatsInputPeriodSaveApiResponse> {
const response = await stClient.post<StatsInputPeriodSaveApiResponse>(
`${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info`,
param,
);
return response.data;
}
/** 통계 입력 기간 삭제 — JSON body. HTTP 메서드·URL은 백엔드 스펙에 맞게 수정 */
public async deleteStatsInputPeriod(
param: StatsInputPeriodDeleteRequest,
): Promise<StatsInputPeriodDeleteApiResponse> {
const response = await stClient.delete<StatsInputPeriodDeleteApiResponse>(
`${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info`,
{ data: param },
);
return response.data;
}
// 입력 - 제출 취소 // 입력 - 제출 취소
public async cancelSubmit( public async cancelSubmit(
param: cancelSubmitRequest, param: cancelSubmitRequest,
@@ -11,6 +11,7 @@ import type {
SewagePipeFactInstallStatusResponse, SewagePipeFactInstallStatusResponse,
SewagePipePipeDiamStatusResponse, SewagePipePipeDiamStatusResponse,
SewagePipePipeTypeFacilityStatusResponse, SewagePipePipeTypeFacilityStatusResponse,
FcltyManholeSummaryResponse,
SewagePipeTotalRequest, SewagePipeTotalRequest,
SewagePipeTotalResponse, SewagePipeTotalResponse,
} from "../model/statistics-total/statistics-sewage-pipe-total-interface"; } from "../model/statistics-total/statistics-sewage-pipe-total-interface";
@@ -19,6 +20,10 @@ import type {
SewerageServiceLevelRequest, SewerageServiceLevelRequest,
SewerageServiceLevelResidentFriendlyFactResponse SewerageServiceLevelResidentFriendlyFactResponse
} from "../model/statistics-total/statistics-service-level-total-interface"; } from "../model/statistics-total/statistics-service-level-total-interface";
import type {
PublicSewageInquirySummaryResponse
} from "../model/statistics-total/statistics-sewage-public-total-interface";
export class StatisticsTotalApiService { export class StatisticsTotalApiService {
public async getSewerageSupplyRateList(params: SewerageSupplyRateRequest): Promise<SewerageSupplyRateResponse> { public async getSewerageSupplyRateList(params: SewerageSupplyRateRequest): Promise<SewerageSupplyRateResponse> {
@@ -84,6 +89,16 @@ export class StatisticsTotalApiService {
); );
return response.data; return response.data;
} }
/** ST00020005 — 맨홀 시설현황(합계) */
public async getSewagePipePipeMnhleStatus(
params: SewagePipeTotalRequest,
): Promise<FcltyManholeSummaryResponse> {
const response = await stClient.get<FcltyManholeSummaryResponse>(
`${ENDPOINT.ST_SERVICE.TOTAL}/sewer-pipe/manhle-status`,
{ params },
);
return response.data;
}
/** ST00030001 — 하수관로 유지관리 > 개보수(합계) */ /** ST00030001 — 하수관로 유지관리 > 개보수(합계) */
public async getSewagePipeMaintainRepairTotal( public async getSewagePipeMaintainRepairTotal(
@@ -106,6 +121,17 @@ export class StatisticsTotalApiService {
); );
return response.data; return response.data;
} }
/** ST00060001 —공공하수처리시설 */
public async getSewagePublicFacillityTotal(
params: SewagePipeMaintainTotalRequest,
): Promise<PublicSewageInquirySummaryResponse> {
const response = await stClient.get<PublicSewageInquirySummaryResponse>(
`${ENDPOINT.ST_SERVICE.TOTAL}/public-sewage`,
{ params },
);
return response.data;
}
} }
export const statisticsTotalStatusApi = new StatisticsTotalApiService(); export const statisticsTotalStatusApi = new StatisticsTotalApiService();
@@ -11,10 +11,18 @@ const IMR_VISIBLE_REGION_COLUMNS = [
"cryrVl", "cryrVl",
] as const; ] as const;
/** ST00020003, ST00020005: 시도·구군·행정구역·관종구분 */ /** ST00020003: 시도·구군·행정구역·관종구분·매설년도 */
const IMR_VISIBLE_REGION_AND_PIPE_SE_COLUMNS = [ const IMR_VISIBLE_REGION_AND_PIPE_COLUMNS = [
...IMR_VISIBLE_REGION_COLUMNS, ...IMR_VISIBLE_REGION_COLUMNS,
"pipeSeNm", "pipeSeNm",
"lyngYr",
] as const;
/** ST00020005: 시도·구군·행정구역·중점관리구역구분·맨홀기준년도 */
const IMR_VISIBLE_REGION_AND_MANH_COLUMNS = [
...IMR_VISIBLE_REGION_COLUMNS,
"emphsMngSeNm",
"manhYr",
] as const; ] as const;
/** ST00060001, ST00100001: 시도·구군·행정구역·시설코드·시설명·주소 */ /** ST00060001, ST00100001: 시도·구군·행정구역·시설코드·시설명·주소 */
@@ -40,8 +48,8 @@ export const IMR_DETAIL_VISIBLE_COLUMNS_BY_STATS_CD: Record<
string, string,
readonly string[] readonly string[]
> = { > = {
ST00020003: [...IMR_VISIBLE_REGION_AND_PIPE_SE_COLUMNS], ST00020003: [...IMR_VISIBLE_REGION_AND_PIPE_COLUMNS],
ST00020005: [...IMR_VISIBLE_REGION_AND_PIPE_SE_COLUMNS], ST00020005: [...IMR_VISIBLE_REGION_AND_MANH_COLUMNS],
ST00060001: [...IMR_VISIBLE_FACILITY_FULL_COLUMNS], ST00060001: [...IMR_VISIBLE_FACILITY_FULL_COLUMNS],
ST00100001: [...IMR_VISIBLE_FACILITY_FULL_COLUMNS], ST00100001: [...IMR_VISIBLE_FACILITY_FULL_COLUMNS],
ST00040001: [...IMR_VISIBLE_FACILITY_NAME_ADDR_COLUMNS], ST00040001: [...IMR_VISIBLE_FACILITY_NAME_ADDR_COLUMNS],
@@ -80,6 +88,18 @@ export const imrDetailGridColumnInfo: ColumnInfo = {
header: { text: "행정구역" }, header: { text: "행정구역" },
width: 100, width: 100,
}, },
{
name: "emphsMngSeNm",
fieldName: "emphsMngSeNm",
header: { text: "중점관리구역구분" },
width: 80,
},
{
name: "manhYr",
fieldName: "manhYr",
header: { text: "맨홀기준년도" },
width: 100,
},
{ {
name: "admdstCd", name: "admdstCd",
fieldName: "admdstCd", fieldName: "admdstCd",
@@ -105,6 +125,12 @@ export const imrDetailGridColumnInfo: ColumnInfo = {
header: { text: "관종구분" }, header: { text: "관종구분" },
width: 80, width: 80,
}, },
{
name: "lyngYr",
fieldName: "lyngYr",
header: { text: "매설년도" },
width: 100,
},
{ {
name: "vrfcTrgtSn", name: "vrfcTrgtSn",
fieldName: "vrfcTrgtSn", fieldName: "vrfcTrgtSn",
@@ -126,10 +152,13 @@ export const imrDetailGridColumnInfo: ColumnInfo = {
"sido", "sido",
"gugun", "gugun",
"adstDong", "adstDong",
"emphsMngSeNm",
"manhYr",
"admdstCd", "admdstCd",
"fcltNm", "fcltNm",
"addr", "addr",
"pipeSeNm", "pipeSeNm",
"lyngYr",
"vrfcTrgtSn", "vrfcTrgtSn",
"cryrVl", "cryrVl",
], ],
@@ -151,6 +151,7 @@ export interface PublicSewageMappingTargetsRequestParams {
/** ST00060002 메인시설 클릭 시 우측 매핑대상 조회 항목 */ /** ST00060002 메인시설 클릭 시 우측 매핑대상 조회 항목 */
export interface PublicSewageMappingTargetItem { export interface PublicSewageMappingTargetItem {
fcltSn: string; fcltSn: string;
admdstCd?: string;
admdstNm: string; admdstNm: string;
swtrmtFcltCd: string; swtrmtFcltCd: string;
swtrmtFcltNm: string; swtrmtFcltNm: string;
@@ -167,6 +168,40 @@ export interface PublicSewageMappingTargetsResponse {
data: PublicSewageMappingTargetItem[]; data: PublicSewageMappingTargetItem[];
} }
/** ST00060002 중심처리시설 매핑 저장 — saveList 항목 */
export interface PublicSewageMainFacilityMappingSaveItem {
fcltSn: string;
admdstCd: string;
admdstNm: string;
swtrmtFcltCd: string;
swtrmtFcltNm: string;
isMainYn: string;
}
/** ST00060002 중심처리시설 매핑 저장 API 요청 */
export interface PublicSewageMainFacilityMappingSaveRequest {
statsYr: string;
sewerBzmnCd: string;
/** 좌측에서 선택한 중심 처리 시설의 행정구역 */
admdstCd: string;
admdstNm: string;
mainFcltSn: string;
mainFcltNm: string;
prxyBzentyNmCn: string;
cnsgnCstVl: number;
wholNope: number;
prxyCtrtCn: string;
userId: string;
saveList: PublicSewageMainFacilityMappingSaveItem[];
deleteList: string[];
}
export interface PublicSewageMainFacilityMappingSaveResponse {
status: string;
message?: string;
data?: string;
}
/** ST00060001 공공하수처리시설 저장 — saveList 항목 */ /** ST00060001 공공하수처리시설 저장 — saveList 항목 */
export interface PublicSewageFacilitySaveItem { export interface PublicSewageFacilitySaveItem {
admdstCd: string; admdstCd: string;
@@ -249,6 +284,7 @@ export interface StatisticsInputPublicSewageFacilityService {
getPublicSewageFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageFacilityResponse>; getPublicSewageFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageFacilityResponse>;
getPublicSewageMainFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageMainFacilityResponse>; getPublicSewageMainFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageMainFacilityResponse>;
getMappingTargets(param: PublicSewageMappingTargetsRequestParams): Promise<PublicSewageMappingTargetsResponse>; getMappingTargets(param: PublicSewageMappingTargetsRequestParams): Promise<PublicSewageMappingTargetsResponse>;
saveMainFacilityMapping(param: PublicSewageMainFacilityMappingSaveRequest): Promise<PublicSewageMainFacilityMappingSaveResponse>;
savePublicSewageFacility(param: PublicSewageFacilitySaveRequest): Promise<PublicSewageFacilitySaveResponse>; savePublicSewageFacility(param: PublicSewageFacilitySaveRequest): Promise<PublicSewageFacilitySaveResponse>;
submitPublicSewageFacility(param: PublicSewageFacilitySubmitRequest): Promise<PublicSewageFacilitySubmitResponse>; submitPublicSewageFacility(param: PublicSewageFacilitySubmitRequest): Promise<PublicSewageFacilitySubmitResponse>;
} }
@@ -35,6 +35,46 @@ export interface ApiResponse<T> {
message?: string; message?: string;
} }
/** 통계 입력 관리 등 — 연도·구분·분류(입력/검증)별 입력 기간 한 행 */
export interface StatsInputPeriodItem {
statsYr: string;
seCd: number;
seType: "INPT" | "VRFC";
inptBgngYmd: string;
inptEndYmd: string;
}
/** `data`가 `StatsInputPeriodItem[]` 인 조회 응답 */
export type StatsInputPeriodListApiResponse = ApiResponse<StatsInputPeriodItem[]>;
/** 통계 입력 기간 목록 조회 요청 쿼리 */
export interface StatsInputPeriodListRequest {
statsYr?: string;
/** 전체 / 입력 / 검증 (SearchForm `clsfCd`와 동일) */
seType?: "ALL" | "INPT" | "VRFC";
}
/** 통계 입력 기간 등록·수정 등 요청 본문 */
export interface StatsInputPeriodSaveRequest {
statsYr: string;
seType: string;
seCd: number;
/** 시작일 (백엔드 형식 문자열, 예: YYYYMMDD) */
inptBgngYmd: string;
/** 종료일 (백엔드 형식 문자열, 예: YYYYMMDD) */
inptEndYmd: string;
rgtrId: string;
}
/** 통계 입력 기간 저장 응답 */
export type StatsInputPeriodSaveApiResponse = ApiResponse<unknown>;
/** 통계 입력 기간 삭제 요청 (필드 구성은 저장과 동일) */
export type StatsInputPeriodDeleteRequest = StatsInputPeriodSaveRequest;
/** 통계 입력 기간 삭제 응답 */
export type StatsInputPeriodDeleteApiResponse = ApiResponse<unknown>;
/** 하수 입력/보급률 등 조회 `data.statusInfo` 공통 (LAE 제출·담당자 정보) */ /** 하수 입력/보급률 등 조회 `data.statusInfo` 공통 (LAE 제출·담당자 정보) */
export interface SewerageStatusInfo { export interface SewerageStatusInfo {
laePicNm: string | null; laePicNm: string | null;
@@ -116,6 +156,7 @@ export interface VerificationResultReportItem {
nowVlCn: string; nowVlCn: string;
compVlCn: string | null; compVlCn: string | null;
prcsYn: string; prcsYn: string;
prcsYnNm: string;
prcsOpnnExpln: string | null; prcsOpnnExpln: string | null;
prcrId: string | null; prcrId: string | null;
rjctOpnnCn: string | null; rjctOpnnCn: string | null;
@@ -305,6 +346,11 @@ export interface StatisticsService {
getSidoList(params?: { instSeCd?: string }): Promise<ApiResponse<SearchCondition>>; getSidoList(params?: { instSeCd?: string }): Promise<ApiResponse<SearchCondition>>;
getSggList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>>; getSggList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>>;
getSggAllList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>>; getSggAllList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>>;
getStatsInputPeriodList(
params?: StatsInputPeriodListRequest,
): Promise<StatsInputPeriodListApiResponse>;
saveStatsInputPeriod(param: StatsInputPeriodSaveRequest): Promise<StatsInputPeriodSaveApiResponse>;
deleteStatsInputPeriod(param: StatsInputPeriodDeleteRequest): Promise<StatsInputPeriodDeleteApiResponse>;
cancelSubmit(param: cancelSubmitRequest): Promise<cancelSubmitResponse>; cancelSubmit(param: cancelSubmitRequest): Promise<cancelSubmitResponse>;
callImrService(param: ImrRequest): Promise<ImrResponse>; callImrService(param: ImrRequest): Promise<ImrResponse>;
submitException(param: SubmitExceptionRequest): Promise<SubmitExceptionResponse>; submitException(param: SubmitExceptionRequest): Promise<SubmitExceptionResponse>;
@@ -32,7 +32,8 @@ export interface SewerPipeMtcItem {
} }
export interface SewerPipeMtcResponseData { export interface SewerPipeMtcResponseData {
mtcStatusList: SewerPipeMtcItem[]; sewerPipeMtcList: SewerPipeMtcItem[];
statsStatusInfo: StatsStatusInfo;
} }
export interface SewerPipeMtcRequestParams { export interface SewerPipeMtcRequestParams {
@@ -96,7 +97,8 @@ export interface SewerPipeDrdgItem {
export interface SewerPipeDrdgResponseData { export interface SewerPipeDrdgResponseData {
drdgStatusList: SewerPipeDrdgItem[]; sewerPipeDrdgList: SewerPipeDrdgItem[];
statsStatusInfo: StatsStatusInfo;
} }
@@ -104,4 +106,13 @@ export type SewerPipeDrdgApiResponse = ApiResponse<SewerPipeDrdgResponseData>;
export interface SewagePipeMtcService { export interface SewagePipeMtcService {
getSewerPipeMtcDrdgItem(params: SewerPipeMtcRequestParams): Promise<SewerPipeDrdgApiResponse>; getSewerPipeMtcDrdgItem(params: SewerPipeMtcRequestParams): Promise<SewerPipeDrdgApiResponse>;
}
export interface StatsStatusInfo {
statsCd?: string;
submitStatusCd?: string;
prvnStatusCd?: string;
rjctOpnnCn?: string;
splmntOpnnCn?: string;
wrterOpnnCn?: string;
} }
@@ -63,7 +63,8 @@ export interface SewagePipeReviewrRegionalItem {
} }
export interface SewagePipeReviewResponseData { export interface SewagePipeReviewResponseData {
sewagePipeList: SewagePipeReviewrRegionalItem[]; regionalStatusList: SewagePipeReviewrRegionalItem[];
statsStatusInfo: StatsStatusInfo;
} }
export interface SewagePipeReviewRequestParams { export interface SewagePipeReviewRequestParams {
@@ -73,7 +74,7 @@ export interface SewagePipeReviewRequestParams {
sewerBzmnCd?: string; sewerBzmnCd?: string;
} }
export type SewagePipeReviewRegionalApiResponse = ApiResponse<SewagePipeReviewrRegionalItem[]> & { export type SewagePipeReviewRegionalApiResponse = ApiResponse<SewagePipeReviewResponseData> & {
message: string; message: string;
}; };
@@ -131,7 +132,12 @@ export interface SewagePipeReviewFacilityItem {
} }
export type SewagePipeFacilityApiResponse = ApiResponse<SewagePipeReviewFacilityItem[]> & { export interface SewagePipeFacilityResponseData {
facilityStatusList: SewagePipeReviewFacilityItem[];
statsStatusInfo: StatsStatusInfo;
}
export type SewagePipeFacilityApiResponse = ApiResponse<SewagePipeFacilityResponseData> & {
message: string; message: string;
}; };
@@ -165,10 +171,11 @@ export interface SewerPipeTypeItem {
export interface SewerPipeTypeResponseData { export interface SewerPipeTypeResponseData {
pipeTypeStatusList: SewerPipeTypeItem[]; pipeTypeList: SewerPipeTypeItem[];
statsStatusInfo: StatsStatusInfo;
} }
export type SewerPipeTypeApiResponse = ApiResponse<SewerPipeTypeItem[]>; export type SewerPipeTypeApiResponse = ApiResponse<SewerPipeTypeResponseData>;
export interface SewagePipeTypeService { export interface SewagePipeTypeService {
@@ -225,9 +232,23 @@ export interface SewerPipeDiamItem {
} }
export type SewerPipeDiamApiResponse = ApiResponse<SewerPipeDiamItem[]>; export interface SewerPipeDiamResponseData {
pipeDiamList: SewerPipeDiamItem[];
statsStatusInfo: StatsStatusInfo;
}
export type SewerPipeDiamApiResponse = ApiResponse<SewerPipeDiamResponseData>;
export interface SewagePipeDiamService { export interface SewagePipeDiamService {
getSewagePipeDiamList(params: SewagePipeReviewRequestParams): Promise<SewerPipeDiamApiResponse>; getSewagePipeDiamList(params: SewagePipeReviewRequestParams): Promise<SewerPipeDiamApiResponse>;
}
export interface StatsStatusInfo {
statsCd?: string;
submitStatusCd?: string;
prvnStatusCd?: string;
rjctOpnnCn?: string;
splmntOpnnCn?: string;
wrterOpnnCn?: string;
} }
@@ -4,6 +4,8 @@ export interface SewagePipeTotalRequest {
statsYr?: string; statsYr?: string;
sewerBzmnCd?: string; sewerBzmnCd?: string;
rowDataYn?: boolean; rowDataYn?: boolean;
summaryYn?: boolean;
summaryYearCnt?: number;
} }
export interface SewagePipeTotalItem { export interface SewagePipeTotalItem {
@@ -234,3 +236,130 @@ export interface SewagePipePipeDiamStatusResponse {
message?: string; message?: string;
data: SewagePipePipeDiamStatusItem[]; data: SewagePipePipeDiamStatusItem[];
} }
/**
* 맨홀 현황 응답 아이템 인터페이스
* DTO의 yy14(2014년 이전) ~ yy25(2024년) 필드를 모두 명시적으로 선언
*/
export interface FcltyManholeSummaryItem {
/* --- 기본 정보 및 행 제어 --- */
ctpvNm: string; // 시도명
sggNm: string; // 시군구명
admdstNm: string; // 행정구역명
admdstCd: string; // 행정구역코드
statsYr: string; // 통계연도
rowType: string; // 데이터 구분 (OLD, CURR, SUMMARY 등)
emphsMngSeCd: string; // 중점관리구역구분코드
emphsMngSeNm: string; // 중점관리구역구분명
totWholMnhlCnt: number;
totStep1MnhlCnt: number;
totStep2MnhlCnt: number;
totStep3MnhlCnt: number;
totStep4MnhlCnt: number;
totStep5MnhlCnt: number;
/* --- yy14 (2014년 이전) --- */
yy14WholMnhlCnt: number; // 합계
yy14Step1MnhlCnt: number; // 1단계
yy14Step2MnhlCnt: number; // 2단계
yy14Step3MnhlCnt: number; // 3단계
yy14Step4MnhlCnt: number; // 4단계
yy14Step5MnhlCnt: number; // 5단계
/* --- yy15 (2014년) --- */
yy15WholMnhlCnt: number;
yy15Step1MnhlCnt: number;
yy15Step2MnhlCnt: number;
yy15Step3MnhlCnt: number;
yy15Step4MnhlCnt: number;
yy15Step5MnhlCnt: number;
/* --- yy16 (2015년) --- */
yy16WholMnhlCnt: number;
yy16Step1MnhlCnt: number;
yy16Step2MnhlCnt: number;
yy16Step3MnhlCnt: number;
yy16Step4MnhlCnt: number;
yy16Step5MnhlCnt: number;
/* --- yy17 (2016년) --- */
yy17WholMnhlCnt: number;
yy17Step1MnhlCnt: number;
yy17Step2MnhlCnt: number;
yy17Step3MnhlCnt: number;
yy17Step4MnhlCnt: number;
yy17Step5MnhlCnt: number;
/* --- yy18 (2017년) --- */
yy18WholMnhlCnt: number;
yy18Step1MnhlCnt: number;
yy18Step2MnhlCnt: number;
yy18Step3MnhlCnt: number;
yy18Step4MnhlCnt: number;
yy18Step5MnhlCnt: number;
/* --- yy19 (2018년) --- */
yy19WholMnhlCnt: number;
yy19Step1MnhlCnt: number;
yy19Step2MnhlCnt: number;
yy19Step3MnhlCnt: number;
yy19Step4MnhlCnt: number;
yy19Step5MnhlCnt: number;
/* --- yy20 (2019년) --- */
yy20WholMnhlCnt: number;
yy20Step1MnhlCnt: number;
yy20Step2MnhlCnt: number;
yy20Step3MnhlCnt: number;
yy20Step4MnhlCnt: number;
yy20Step5MnhlCnt: number;
/* --- yy21 (2020년) --- */
yy21WholMnhlCnt: number;
yy21Step1MnhlCnt: number;
yy21Step2MnhlCnt: number;
yy21Step3MnhlCnt: number;
yy21Step4MnhlCnt: number;
yy21Step5MnhlCnt: number;
/* --- yy22 (2021년) --- */
yy22WholMnhlCnt: number;
yy22Step1MnhlCnt: number;
yy22Step2MnhlCnt: number;
yy22Step3MnhlCnt: number;
yy22Step4MnhlCnt: number;
yy22Step5MnhlCnt: number;
/* --- yy23 (2022년) --- */
yy23WholMnhlCnt: number;
yy23Step1MnhlCnt: number;
yy23Step2MnhlCnt: number;
yy23Step3MnhlCnt: number;
yy23Step4MnhlCnt: number;
yy23Step5MnhlCnt: number;
/* --- yy24 (2023년) --- */
yy24WholMnhlCnt: number;
yy24Step1MnhlCnt: number;
yy24Step2MnhlCnt: number;
yy24Step3MnhlCnt: number;
yy24Step4MnhlCnt: number;
yy24Step5MnhlCnt: number;
/* --- yy25 (2024년) --- */
yy25WholMnhlCnt: number;
yy25Step1MnhlCnt: number;
yy25Step2MnhlCnt: number;
yy25Step3MnhlCnt: number;
yy25Step4MnhlCnt: number;
yy25Step5MnhlCnt: number;
}
/**
* 맨홀 현황 API 공통 응답 규격
*/
export interface FcltyManholeSummaryResponse {
status: string; // success / error 등
message?: string; // 오류 메시지 등
data: FcltyManholeSummaryItem[]; // 실제 리스트 데이터
}
@@ -0,0 +1,103 @@
export interface PublicSewageInquirySummaryRequest {
ctpvNm?: string;
sggNm?: string;
statsYr?: string;
swtrmtFcltNm?: string;
sewerBzmnCd?: string;
rowDataYn?: boolean;
capacityGbn?: string;
summaryYn?: boolean;
summaryYearCnt?: number;
}
export interface PublicSewageInquirySummaryItem {
// 기본 정보 및 행정구역
ctpvNm: string;
sggNm: string;
statsYr: string;
admdstCd: string;
admdstNm: string;
swtrmtFcltNm: string;
rowType: string;
fcltPstnCn: string;
// 시설 용량 (㎥/일)
prcsFcltCpct: number;
prcsFcltPhysCpct: number;
prcsFcltBoilCpct: number;
prcsFcltAltdCpct: number;
// 유입 및 처리량 (㎥/일)
inflwSwageQnt: number;
inflwSwagePhysQnt: number;
inflwSwageBoilQnt: number;
inflwSwageAltdQnt: number;
prcsQnt: number;
prcsPhysCpct: number;
prcsBoilCpct: number;
prcsAltdCpct: number;
// 수질 정보 (유입/방류)
inflwBodVl: number;
dschgBodVl: number;
inflwTocQnt: number;
dschgTocQnt: number;
inflwSsQnt: number;
dschgSsQnt: number;
inflwTnVl: number;
dschgTnVl: number;
inflwTpVl: number;
dschgTpVl: number;
inflwGrssClfgVl: number;
dschgGrssClfgVl: number;
// 처리 효율 및 방법
prcsRt: number;
prcsLodQnt: number;
prcsMthdExpln: string;
// 연계 처리량 (㎥/일)
wholHmnQnt: number;
wholLvstQnt: number;
wholEfntQnt: number;
wholEtcQnt: number;
// 부가 정보
telNo: string;
cmptnStrtYmd: string;
oprtngStrtYmd: string;
wholBizVl: number;
eflntDsnfMthdExpln: string;
// 방류수역 정보
wtsysNm: string;
trbtrNm: string;
dtlUnitZoneCn: string;
rgnSeNm: string;
msirbCn: string;
goalWqNo: string;
// 에너지 및 위탁현황
engySrlneRt: number;
engyItslfPrdctnQnt: number;
anlEngyWholUseQnt: number;
anlElpwrUseQnt: number;
itslfEngyUseQnt: number;
swtrmtEmsnQnt: number;
operMnbdCn: string;
// 위탁현황
prxyBzentyNmCn: string;
cnsgnCstVl: number;
prxyCtrtCn: string;
intgPrcsFcltCn: string;
wholNope: number;
sftyAcdntEvntcsCnt: number;
}
export interface PublicSewageInquirySummaryResponse {
status: string;
message: string;
data: PublicSewageInquirySummaryItem[];
}
@@ -34,7 +34,7 @@ export const hbArticleColumnInfo: ColumnInfo = {
{ {
name: "ipcrCntVl", name: "ipcrCntVl",
fieldName: "ipcrCntVl", fieldName: "ipcrCntVl",
header: { text: "μ" }, header: { text: "중요도 계수(μ)" },
editable: false, editable: false,
sortdisable: false, sortdisable: false,
valueType: ValueType.NUMBER, valueType: ValueType.NUMBER,
@@ -43,7 +43,7 @@ export const hbArticleColumnInfo: ColumnInfo = {
{ {
name: "pctlCrtrVl", name: "pctlCrtrVl",
fieldName: "pctlCrtrVl", fieldName: "pctlCrtrVl",
header: { text: "Percentile" }, header: { text: "백분위수 기준값(pct)" },
editable: false, editable: false,
sortdisable: false, sortdisable: false,
valueType: ValueType.NUMBER, valueType: ValueType.NUMBER,
@@ -52,7 +52,7 @@ export const hbArticleColumnInfo: ColumnInfo = {
{ {
name: "scopCntCntVl", name: "scopCntCntVl",
fieldName: "scopCntCntVl", fieldName: "scopCntCntVl",
header: { text: "A" }, header: { text: "스케일 조정 계수(A)" },
editable: false, editable: false,
sortdisable: false, sortdisable: false,
valueType: ValueType.NUMBER, valueType: ValueType.NUMBER,
@@ -61,7 +61,7 @@ export const hbArticleColumnInfo: ColumnInfo = {
{ {
name: "thrsCntVl", name: "thrsCntVl",
fieldName: "thrsCntVl", fieldName: "thrsCntVl",
header: { text: "C" }, header: { text: "임계 계수(c)" },
editable: false, editable: false,
sortdisable: false, sortdisable: false,
valueType: ValueType.NUMBER, valueType: ValueType.NUMBER,