[st] - feat: 01.제출 후 예외사유 제출 / 02. 작업 중

This commit is contained in:
2026-05-07 15:33:04 +09:00
parent 9f9c6f30cb
commit 638c99ac90
22 changed files with 1504 additions and 351 deletions
@@ -41,7 +41,10 @@ function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueT
if (col.computed) {
const hasFields = "fields" in col.computed && col.computed.fields.length > 0;
const hasNumeratorDenominator = "numeratorFields" in col.computed && col.computed.numeratorFields.length > 0 && col.computed.denominatorFields.length > 0;
if (hasFields || hasNumeratorDenominator) {
const hasMaxOfFieldAndSum = "baseField" in col.computed && "sumFields" in col.computed && col.computed.sumFields.length > 0;
const hasDifferenceOfSums = "minuendFields" in col.computed && "subtrahendFields" in col.computed && col.computed.minuendFields.length > 0;
const hasSumOfMaxGroups = "groups" in col.computed && Array.isArray(col.computed.groups) && col.computed.groups.length > 0;
if (hasFields || hasNumeratorDenominator || hasMaxOfFieldAndSum || hasDifferenceOfSums || hasSumOfMaxGroups) {
field.valueCallback = getComputedValueCallback(col.computed) as (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number;
}
}
+17 -1
View File
@@ -292,7 +292,19 @@ function buildFieldsFromColumns(
"numeratorFields" in col.computed &&
col.computed.numeratorFields.length > 0 &&
col.computed.denominatorFields.length > 0;
if (hasFields || hasNumeratorDenominator) {
const hasMaxOfFieldAndSum =
"baseField" in col.computed &&
"sumFields" in col.computed &&
col.computed.sumFields.length > 0;
const hasDifferenceOfSums =
"minuendFields" in col.computed &&
"subtrahendFields" in col.computed &&
col.computed.minuendFields.length > 0;
const hasSumOfMaxGroups =
"groups" in col.computed &&
Array.isArray(col.computed.groups) &&
col.computed.groups.length > 0;
if (hasFields || hasNumeratorDenominator || hasMaxOfFieldAndSum || hasDifferenceOfSums || hasSumOfMaxGroups) {
field.valueCallback = getComputedValueCallback(
col.computed,
) as (
@@ -1215,6 +1227,10 @@ const DataGrid = ({
)
: <span className="animate-pulse">검증중</span>}
</span>
<span>
반려사유:{" "}
{statusInfo.rjctOpnnCn ? statusInfo.rjctOpnnCn : "-"}
</span>
<span>
제출일:{" "}
{statusInfo.laeSbmsnYmd
@@ -215,7 +215,10 @@ function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueT
if (col.computed) {
const hasFields = "fields" in col.computed && col.computed.fields.length > 0;
const hasNumeratorDenominator = "numeratorFields" in col.computed && col.computed.numeratorFields.length > 0 && col.computed.denominatorFields.length > 0;
if (hasFields || hasNumeratorDenominator) {
const hasMaxOfFieldAndSum = "baseField" in col.computed && "sumFields" in col.computed && col.computed.sumFields.length > 0;
const hasDifferenceOfSums = "minuendFields" in col.computed && "subtrahendFields" in col.computed && col.computed.minuendFields.length > 0;
const hasSumOfMaxGroups = "groups" in col.computed && Array.isArray(col.computed.groups) && col.computed.groups.length > 0;
if (hasFields || hasNumeratorDenominator || hasMaxOfFieldAndSum || hasDifferenceOfSums || hasSumOfMaxGroups) {
field.valueCallback = getComputedValueCallback(col.computed) as (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number;
}
}
@@ -24,6 +24,7 @@ import {
import DataGrid from "@/components/module/DataGrid";
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
import type { CellEditedCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
import { getUserInfo } from "../../../../../packages/common/components/src/api";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
import { useAlertStore } from "@/shared/lib";
@@ -146,20 +147,15 @@ export function VerificationResultDialog({
const normalGridRef = useRef<RealGridReact | null>(null);
const imrGridRef = useRef<RealGridReact | null>(null);
/** 메인 입력 화면처럼 편집 값은 그리드에 두고, 제출 시에만 스냅샷 조회 → Enter 시 rows 재주입 충돌 방지 */
const getNormalGridDataRef = useRef<(() => VerificationResultGridRow[]) | null>(null);
const getImrGridDataRef = useRef<(() => VerificationResultGridRow[]) | null>(null);
const alert = useAlertStore((state) => state.setMessage);
const onNormalPrcsOpnnExplnEdited = useCallback((params: CellEditedCallbackParams) => {
if (params.fieldName !== "prcsOpnnExpln") return;
const text = String(params.newValue ?? "");
const hasValue = text.trim().length > 0;
/** DataGrid는 onCellEdited 시 React rowData를 갱신하지 않아, 제출용 상태를 여기서 맞춤 */
setNormalRows((prev) => {
const next = [...prev];
const row = next[params.itemIndex];
if (!row) return prev;
next[params.itemIndex] = { ...row, prcsOpnnExpln: text };
return next;
});
setRowCheckByItemIndex(normalGridRef, params.itemIndex, hasValue);
setNormalCheckedIndices((prev) => {
const next = new Set(prev);
@@ -173,13 +169,6 @@ export function VerificationResultDialog({
if (params.fieldName !== "prcsOpnnExpln") return;
const text = String(params.newValue ?? "");
const hasValue = text.trim().length > 0;
setImrRows((prev) => {
const next = [...prev];
const row = next[params.itemIndex];
if (!row) return prev;
next[params.itemIndex] = { ...row, prcsOpnnExpln: text };
return next;
});
setRowCheckByItemIndex(imrGridRef, params.itemIndex, hasValue);
setImrCheckedIndices((prev) => {
const next = new Set(prev);
@@ -358,25 +347,60 @@ export function VerificationResultDialog({
return () => cancelAnimationFrame(id);
}, [imrRows, imrLoading]);
const handleExceptionSubmitClick = useCallback(() => {
const handleExceptionSubmitClick = useCallback(async () => {
const nvGrid = getNormalGridDataRef.current?.() ?? [];
const imGrid = getImrGridDataRef.current?.() ?? [];
const payload: VerificationExceptionSubmitItem[] = [];
const pushIfHasExpln = (row: VerificationResultGridRow | undefined) => {
if (!row) return;
const prcsOpnnExpln = String(row.prcsOpnnExpln ?? "").trim();
if (!prcsOpnnExpln) return;
payload.push({
vrfcSn: Number(row.vrfcSn),
prcsOpnnExpln,
const pushChecked = (
indices: Set<number>,
gridRows: VerificationResultGridRow[],
idRows: VerificationResultGridRow[],
) => {
indices.forEach((idx) => {
const prcsOpnnExpln = String(gridRows[idx]?.prcsOpnnExpln ?? "").trim();
if (!prcsOpnnExpln) return;
const vrfcSn = Number(idRows[idx]?.vrfcSn);
if (!Number.isFinite(vrfcSn)) return;
payload.push({ vrfcSn, prcsOpnnExpln });
});
};
normalCheckedIndices.forEach((idx) => pushIfHasExpln(normalRows[idx]));
imrCheckedIndices.forEach((idx) => pushIfHasExpln(imrRows[idx]));
pushChecked(normalCheckedIndices, nvGrid, normalRows);
pushChecked(imrCheckedIndices, imGrid, imrRows);
if (payload.length === 0) {
alert("작성된 예외 제출 항목이 없습니다.");
return;
}
console.log(payload);
}, [alert, imrCheckedIndices, imrRows, normalCheckedIndices, normalRows]);
if (!stableQueryContext) {
alert("조회 조건이 없어 예외 제출을 할 수 없습니다.");
return;
}
try {
const userInfo = await getUserInfo();
const response = await statisticsApi.SubmitVerifyException({
sewerBzmnCd: stableQueryContext.sewerBzmnCd,
statsYr: stableQueryContext.statsYr,
statsCd: stableQueryContext.statsCd,
userId: userInfo.userUnqId,
exceptionList: payload,
});
if (response.status === "success") {
alert("예외 제출 처리되었습니다.");
onOpenChange(false);
} else {
alert(response.message ?? "예외 제출 처리 중 오류가 발생했습니다.");
}
} catch {
alert("예외 제출 처리 중 오류가 발생했습니다.");
}
}, [
alert,
imrCheckedIndices,
imrRows,
loadInitial,
normalCheckedIndices,
normalRows,
stableQueryContext,
]);
const renderGrid = (
rows: VerificationResultGridRow[],
@@ -386,6 +410,7 @@ export function VerificationResultDialog({
onPageIndexChange: (page: number) => void,
loading: boolean,
gridRef: MutableRefObject<RealGridReact | null>,
getGridDataRef: MutableRefObject<(() => VerificationResultGridRow[]) | null>,
onPrcsOpnnExplnCellEdited: (params: CellEditedCallbackParams) => void,
setCheckedIndices: Dispatch<SetStateAction<Set<number>>>,
) => {
@@ -404,6 +429,7 @@ export function VerificationResultDialog({
showHeaderCheckbox
enableSort={false}
getGridRef={gridRef}
getGridDataRef={getGridDataRef}
onCellEdited={onPrcsOpnnExplnCellEdited}
onItemChecked={(itemIndex, checked) => {
setCheckedIndices((prev) => {
@@ -467,6 +493,7 @@ export function VerificationResultDialog({
handleNormalPageChange,
normalLoading,
normalGridRef,
getNormalGridDataRef,
onNormalPrcsOpnnExplnEdited,
setNormalCheckedIndices,
)}
@@ -481,6 +508,7 @@ export function VerificationResultDialog({
handleImrPageChange,
imrLoading,
imrGridRef,
getImrGridDataRef,
onImrPrcsOpnnExplnEdited,
setImrCheckedIndices,
)}
@@ -10,6 +10,8 @@ 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";
@@ -26,29 +28,43 @@ import { doList } from "@/components/util/column/input/DoList";
import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service";
import type {
SeweragePipeInputFactInstallStatusSaveRequest,
SeweragePipeInputManholeStatusItem,
SeweragePipeInputManholeStatusSaveRequest,
SeweragePipeInputPipeDiameterStatusSaveRequest,
SeweragePipeInputPipeTypeStatusItem,
SeweragePipeInputPipeTypeStatusSaveRequest,
SeweragePipeInputRegionalFactStatusSaveRequest,
SeweragePipeInputStatusInfo,
SeweragePipeInputRegionalFactStatusSaveItem,
SeweragePipeInputVerificationResult,
SeweragePipeRequestParams,
} from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface";
import type {
cancelSubmitRequest,
ImrStatusResponse,
SewerageStatusInfo,
SubmitExceptionRequest,
} from "@/entities/statistics/model/statistics-interface";
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
import {
type CheckInputPeriodItem,
getVerificationPeriodActionBlockedMessage,
isInputPeriodCurrent,
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
import { FactInstallStatusInpColumn } from "@/components/util/column/input/sewagePipe/FactInstallStatusInpColumn";
import { ManholeStatusInpColumn } from "@/components/util/column/input/sewagePipe/ManholeStatusInpColumn";
import { PipeDiameterStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeDiameterStatusInpColumn";
import { PipeTypeStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeTypeStatusInpColumn";
import { RegionalFactStatusInpColumn } from "@/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn";
import { toGridRows } from "@/components/util/grid/gridFunction";
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type { cancelSubmitRequest } from "@/entities/statistics/model/statistics-interface";
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
import { useTabStore } from "@/store/tab-store";
/** 탭별 status + 입력기간으로 편집 가능 여부 */
const computeTabEnterable = (
statusInfo: SeweragePipeInputStatusInfo | null | undefined,
statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean,
): boolean => {
if (statusInfo?.laeSbmsnSttsCd) {
@@ -73,11 +89,192 @@ function buildSewagePipeSearchParam(
};
}
const getPipeTypeLastDataYear = () => {
const lastDataYear = new Date().getFullYear() - 1;
return Math.max(1995, lastDataYear);
};
const getManholeLastDataYear = () => {
const lastDataYear = new Date().getFullYear() - 1;
return Math.max(2014, lastDataYear);
};
/** 관종별 신 응답(yearlyStatList) -> 그리드 y{year}* 필드로 정규화 */
function normalizePipeTypeStatusRow(
row: SeweragePipeInputPipeTypeStatusItem,
ctpvNm: string,
): Record<string, string | number | SeweragePipeInputVerificationResult | undefined> {
const lastDataYear = getPipeTypeLastDataYear();
const {
yearlyStatList,
lyngPrlgWhol,
dmltnPrlgWhol,
...rest
} = row;
const yearFields: Record<string, number> = {};
for (let year = 1995; year <= lastDataYear; year++) {
yearFields[`y${year}PrlgWhol`] = 0;
yearFields[`y${year}LyngPrlg`] = 0;
yearFields[`y${year}DmltnPrlg`] = 0;
}
for (const y of yearlyStatList ?? []) {
const year = Number(y.lyngYr);
if (!Number.isFinite(year)) continue;
const targetYear = Math.min(lastDataYear, Math.max(1995, year));
yearFields[`y${targetYear}PrlgWhol`] =
(yearFields[`y${targetYear}PrlgWhol`] ?? 0) + Number(y.prlgWhol ?? 0);
yearFields[`y${targetYear}LyngPrlg`] =
(yearFields[`y${targetYear}LyngPrlg`] ?? 0) + Number(y.lyngPrlg ?? 0);
yearFields[`y${targetYear}DmltnPrlg`] =
(yearFields[`y${targetYear}DmltnPrlg`] ?? 0) + Number(y.dmltnPrlg ?? 0);
}
return {
ctpvNm,
...rest,
lyngPrlg: Number(row.lyngPrlg ?? lyngPrlgWhol ?? 0),
dmltnPrlg: Number(row.dmltnPrlg ?? dmltnPrlgWhol ?? 0),
...yearFields,
};
}
/** 맨홀 신 응답(yearlyStats) -> 그리드 yy{14..25}* 필드로 정규화 */
function normalizeManholeStatusRow(
row: SeweragePipeInputManholeStatusItem,
ctpvNm: string,
): Record<string, string | number | SeweragePipeInputVerificationResult | undefined> {
const maxYy = getManholeLastDataYear() % 100;
const { yearlyStats, ...rest } = row;
const yearFields: Record<string, number> = {};
for (let yy = 14; yy <= maxYy; yy++) {
yearFields[`yy${yy}WholMnhlCnt`] = 0;
yearFields[`yy${yy}Step1MnhlCnt`] = 0;
yearFields[`yy${yy}Step2MnhlCnt`] = 0;
yearFields[`yy${yy}Step3MnhlCnt`] = 0;
yearFields[`yy${yy}Step4MnhlCnt`] = 0;
yearFields[`yy${yy}Step5MnhlCnt`] = 0;
}
for (const y of yearlyStats ?? []) {
const year = Number(y.manhYr);
if (!Number.isFinite(year)) continue;
const yy = year % 100;
if (yy < 14 || yy > maxYy) continue;
yearFields[`yy${yy}WholMnhlCnt`] = Number(y.wholMnhlCnt ?? 0);
yearFields[`yy${yy}Step1MnhlCnt`] = Number(y.step1MnhlCnt ?? 0);
yearFields[`yy${yy}Step2MnhlCnt`] = Number(y.step2MnhlCnt ?? 0);
yearFields[`yy${yy}Step3MnhlCnt`] = Number(y.step3MnhlCnt ?? 0);
yearFields[`yy${yy}Step4MnhlCnt`] = Number(y.step4MnhlCnt ?? 0);
yearFields[`yy${yy}Step5MnhlCnt`] = Number(y.step5MnhlCnt ?? 0);
}
return {
ctpvNm,
...rest,
...yearFields,
};
}
/** ST00020006(연결)은 행이 좌·우 그리드 키에만 있음 */
type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: SeweragePipeInputVerificationResult;
};
const REGIONAL_FACT_TAB_KEY = "ST00020001";
function hasRegionalFactVerificationErrors(verification?: SeweragePipeInputVerificationResult | null): boolean {
if (!verification) return false;
return Object.values(verification).some((errors) => Array.isArray(errors) && errors.length > 0);
}
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
async function waitForImrJobCompleted(
jobId: string,
getStatus: (id: string) => Promise<ImrStatusResponse>,
options?: {
pollIntervalMs?: number;
maxAttempts?: number;
onProgress?: (jobProgress: number) => void;
},
): Promise<void> {
const pollIntervalMs = options?.pollIntervalMs ?? 2000;
const maxAttempts = options?.maxAttempts ?? 150;
for (let i = 0; i < maxAttempts; i++) {
const res = await getStatus(jobId);
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0)));
if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return;
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") {
throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`);
}
await sleep(pollIntervalMs);
}
throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다.");
}
function buildRegionalFactSubmitListFromGrid(
currentData: Record<string, string | number>[],
): SeweragePipeInputRegionalFactStatusSaveItem[] {
const columnFormatMap = new Map<string, string>();
RegionalFactStatusInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) {
columnFormatMap.set(col.fieldName, col.dataFormat);
}
});
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === "") return 0;
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
return currentData.map((d) => ({
admdstCd: String(d.admdstCd ?? ""),
sggNm: String(d.sggNm ?? ""),
admdstNm: String(d.admdstNm ?? ""),
goalYr: String(d.goalYr ?? ""),
wholPplMtncPlanPrlg: formatNumber(d.wholPplMtncPlanPrlg, "wholPplMtncPlanPrlg"),
wholPlanPrlg: formatNumber(d.wholPlanPrlg, "wholPlanPrlg"),
wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"),
swagePplPsplyRt: formatNumber(d.swagePplPsplyRt, "swagePplPsplyRt"),
cbsysPplMtncPlanArea: formatNumber(d.cbsysPplMtncPlanArea, "cbsysPplMtncPlanArea"),
cbsysPplMtncPlanPrlg: formatNumber(d.cbsysPplMtncPlanPrlg, "cbsysPplMtncPlanPrlg"),
cbsysPlanPrlg: formatNumber(d.cbsysPlanPrlg, "cbsysPlanPrlg"),
cbsysFcltPrlg: formatNumber(d.cbsysFcltPrlg, "cbsysFcltPrlg"),
cbsysSqrePrlg: formatNumber(d.cbsysSqrePrlg, "cbsysSqrePrlg"),
cbsysCiclPrlg: formatNumber(d.cbsysCiclPrlg, "cbsysCiclPrlg"),
cbsysOpnPrlg: formatNumber(d.cbsysOpnPrlg, "cbsysOpnPrlg"),
cbsysGttrPrlg: formatNumber(d.cbsysGttrPrlg, "cbsysGttrPrlg"),
drtwPplMtncPlanArea: formatNumber(d.drtwPplMtncPlanArea, "drtwPplMtncPlanArea"),
drtwPplMtncPlanPrlg: formatNumber(d.drtwPplMtncPlanPrlg, "drtwPplMtncPlanPrlg"),
drtwPplPlanPrlg: formatNumber(d.drtwPplPlanPrlg, "drtwPplPlanPrlg"),
drtwPplFcltPrlg: formatNumber(d.drtwPplFcltPrlg, "drtwPplFcltPrlg"),
drtwPplSqrePrlg: formatNumber(d.drtwPplSqrePrlg, "drtwPplSqrePrlg"),
drtwPplCiclPrlg: formatNumber(d.drtwPplCiclPrlg, "drtwPplCiclPrlg"),
exlnPplMtncPlanArea: formatNumber(d.exlnPplMtncPlanArea, "exlnPplMtncPlanArea"),
exlnPplMtncPlanPrlg: formatNumber(d.exlnPplMtncPlanPrlg, "exlnPplMtncPlanPrlg"),
exlnPplPlanPrlg: formatNumber(d.exlnPplPlanPrlg, "exlnPplPlanPrlg"),
exlnPplFcltPrlg: formatNumber(d.exlnPplFcltPrlg, "exlnPplFcltPrlg"),
exlnPplSqrePrlg: formatNumber(d.exlnPplSqrePrlg, "exlnPplSqrePrlg"),
exlnPplCiclPrlg: formatNumber(d.exlnPplCiclPrlg, "exlnPplCiclPrlg"),
exlnPplOpnPrlg: formatNumber(d.exlnPplOpnPrlg, "exlnPplOpnPrlg"),
exlnPplGttrPrlg: formatNumber(d.exlnPplGttrPrlg, "exlnPplGttrPrlg"),
cbsysMnhlCnt: formatNumber(d.cbsysMnhlCnt, "cbsysMnhlCnt"),
sprsysDrtwMnhlCnt: formatNumber(d.sprsysDrtwMnhlCnt, "sprsysDrtwMnhlCnt"),
sprsysExlnMnhlCnt: formatNumber(d.sprsysExlnMnhlCnt, "sprsysExlnMnhlCnt"),
drtwRcvprpCnt: formatNumber(d.drtwRcvprpCnt, "drtwRcvprpCnt"),
exlnRcvprpCnt: formatNumber(d.exlnRcvprpCnt, "exlnRcvprpCnt"),
stofcbrCnt: formatNumber(d.stofcbrCnt, "stofcbrCnt"),
exlnOtflCnt: formatNumber(d.exlnOtflCnt, "exlnOtflCnt"),
sewerLdgrEdpPrlg: formatNumber(d.sewerLdgrEdpPrlg, "sewerLdgrEdpPrlg"),
sewerLdgrEdpRt: formatNumber(d.sewerLdgrEdpRt, "sewerLdgrEdpRt"),
rmrk: (d.rmrk as string | null) ?? null,
}));
}
function tabRowDataExists(
m: Map<string, Record<string, string | number>[]>,
tabKey: string,
@@ -112,7 +309,7 @@ function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
* @param treatAsSearched (hasRow || 이 값) 일 때 카드 후보 — load 내부에서는 searchDoneRef 반영용
*/
function computeTabToolbarSnapshot(
statusInfo: SeweragePipeInputStatusInfo | null,
statusInfo: SewerageStatusInfo | null,
hasRowData: boolean,
treatAsSearched: boolean,
searchContext: SearchFormValues | null,
@@ -140,9 +337,10 @@ const SewagePipeInp = () => {
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
const [lastSearchedYear, setLastSearchedYear] = useState<string | null>(null);
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
const [tabStatusInfoMap, setTabStatusInfoMap] = useState<Map<string, SeweragePipeInputStatusInfo | null>>(new Map());
// const [showDailog, setShowDailog] = useState<boolean>(false);
// const [isExceptionSubmitFlow, setIsExceptionSubmitFlow] = useState(false);
const [tabStatusInfoMap, setTabStatusInfoMap] = useState<Map<string, SewerageStatusInfo | null>>(new Map());
const [showDailog, setShowDailog] = useState<boolean>(false);
const [isExceptionSubmitFlow, setIsExceptionSubmitFlow] = useState(false);
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
const [statsCd, setStatsCd] = useState<string>('');
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
@@ -158,12 +356,16 @@ const SewagePipeInp = () => {
const { confirm } = useConfirm();
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 { isAdminInfo } = useAdminStore();
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const getFactInstallGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const getPipeTypeGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const getPipeDiameterGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const getManholeGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const searchParamRef = useRef<SeweragePipeRequestParams | null>(null);
/** 검색 시 checkInputPeriod 결과 — 탭 전환 지연 조회 시 편집 가능 여부와 동일하게 맞춤 */
const periodEnterableRef = useRef(true);
@@ -259,7 +461,7 @@ const SewagePipeInp = () => {
const commitToolbarSnapshotForLoadedTab = (
tabKey: string,
statusInfo: SeweragePipeInputStatusInfo | null,
statusInfo: SewerageStatusInfo | null,
hasRowData: boolean,
searchContext: SearchFormValues,
) => {
@@ -354,10 +556,9 @@ const SewagePipeInp = () => {
const response = await statisticsInputSewPipeApiService.getPipeTypeStatus(searchParam);
if (response.status === 'success') {
const commonInfo = response.data.commonInfo;
const convertedData = (response.data.pipeTypeStatusList ?? []).map((d) => ({
ctpvNm: commonInfo.ctpvNm,
...d,
}));
const convertedData = (response.data.pipeTypeStatusList ?? []).map((d) =>
normalizePipeTypeStatusRow(d, commonInfo.ctpvNm),
);
applyRowMap((m) => m.set('ST00020003', toGridRows(convertedData)));
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
@@ -402,10 +603,9 @@ const SewagePipeInp = () => {
const response = await statisticsInputSewPipeApiService.getManholeStatus(searchParam);
if (response.status === 'success') {
const commonInfo = response.data.commonInfo;
const convertedData = (response.data.manholeStatusList ?? []).map((d) => ({
ctpvNm: commonInfo.ctpvNm,
...d,
}));
const convertedData = (response.data.manholeStatusList ?? []).map((d) =>
normalizeManholeStatusRow(d, commonInfo.ctpvNm),
);
applyRowMap((m) => m.set('ST00020005', toGridRows(convertedData)));
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
@@ -508,8 +708,8 @@ const SewagePipeInp = () => {
const tabKeyToLoad = forcedTab
? forcedTab
: yearChangedFromLastSearch
? firstTabKey
: activeTab;
? firstTabKey
: activeTab;
if (forcedTab) {
activeTabRef.current = forcedTab;
@@ -587,14 +787,230 @@ const SewagePipeInp = () => {
}
};
const submitFunc = () => {
const getTabGridRowsForVerification = (tabKey: string): Record<string, string | number>[] => {
switch (tabKey) {
case "ST00020001":
return getGridDataRef.current?.() ?? [];
case "ST00020002":
return getFactInstallGridDataRef.current?.() ?? [];
case "ST00020003":
return getPipeTypeGridDataRef.current?.() ?? [];
case "ST00020004":
return getPipeDiameterGridDataRef.current?.() ?? [];
case "ST00020005":
return getManholeGridDataRef.current?.() ?? [];
case "ST00020006":
return [
...(rowDataMap.get("treatmentPlantConnectionInp") ?? []),
...(rowDataMap.get("adminAreaConnectionInp") ?? []),
];
default:
return [];
}
};
const SubmitExceptionFunc = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
const tabKey = activeTabRef.current;
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
if (verificationBlocked) {
alert(verificationBlocked);
return;
}
console.log('submit');
// 제출 로직
const userInfo = await getUserInfo();
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? statsCd;
const params: SubmitExceptionRequest = {
...submitInfo,
sewerBzmnCd: searchValues?.bsnmCd ?? "",
ctpvNm: searchValues?.sido ?? "",
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ""),
statsYr: searchValues?.year ?? "",
userId: userInfo.userUnqId,
statsCd: statsCdForTab,
};
try {
setIsLoading(true);
const response = await statisticsApi.submitException(params);
if (response.status === "success") {
const values = lastSearchedValues ?? searchValues;
if (values && searchParamRef.current) {
await loadSewagePipeActiveTabData(tabKey, searchParamRef.current, values, true, false);
}
alert("제출되었습니다.");
} else {
alert(response.message || "데이터 제출 중 오류가 발생했습니다.");
}
} catch {
alert("데이터 제출 중 오류가 발생했습니다.");
} finally {
setIsLoading(false);
}
};
const submitFunc = async (isException: boolean) => {
const tabKey = activeTabRef.current;
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
if (verificationBlocked) {
alert(verificationBlocked);
return;
}
if (!isException) {
const currentData = getTabGridRowsForVerification(tabKey);
const rowsWithVerificationErrors = currentData.filter((d) =>
hasRegionalFactVerificationErrors((d as GridRowWithVerification).verificationResult),
);
if (rowsWithVerificationErrors.length > 0) {
alert("데이터에 오류가 있습니다.\n수정 후 제출해주세요.");
return;
}
if (await confirm("제출 하시겠습니까?")) {
setIsExceptionSubmitFlow(false);
setShowDailog(true);
}
} else {
if (await confirm("예외 제출 시 검증 결과에 오류가 있어도 제출 처리됩니다.")) {
setIsExceptionSubmitFlow(true);
setShowDailog(true);
}
}
};
const executeExceptionSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
await SubmitExceptionFunc(submitInfo);
};
const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
if (verificationBlocked) {
alert(verificationBlocked);
return;
}
const tabKey = activeTabRef.current;
const values = lastSearchedValues ?? searchValues;
if (!values?.year) {
alert("검색 후 제출해 주세요.");
return;
}
const searchParam = searchParamRef.current;
if (!searchParam) {
alert("먼저 조회를 진행해주세요.");
return;
}
try {
showGlobalProgress({ label: "서버에 요청 중...", progress: 0 });
const userInfo = await getUserInfo();
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? statsCd;
const statusForTab = tabStatusInfoMap.get(tabKey) ?? null;
switch (tabKey) {
case REGIONAL_FACT_TAB_KEY: {
const currentData = getGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert("제출할 데이터가 없습니다.");
return;
}
const submitList = buildRegionalFactSubmitListFromGrid(currentData);
const requestData = {
statsYr: values.year ?? "",
ctpvNm: values.sido ?? "",
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
submitList,
laePicNm: submitInfo.laePicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
};
setGlobalProgressLabel("일반 검증 진행 중...");
const response =
await statisticsInputSewPipeApiService.submitRegionalFactStatus(requestData);
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: values.bsnmCd ?? "",
stats_cd: statsCdForTab,
stats_yr: values.year ?? "",
lae_sbmsn_stts_cd: statusForTab?.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 loadSewagePipeActiveTabData(
tabKey,
buildSewagePipeSearchParam(values),
values,
true,
false,
);
setVerificationResultDialogOpen(true);
} catch (e) {
await loadSewagePipeActiveTabData(
tabKey,
buildSewagePipeSearchParam(values),
values,
true,
false,
);
const msg =
e instanceof Error && e.message
? e.message
: "IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.";
alert(msg);
}
} else {
setGlobalProgress(100);
await loadSewagePipeActiveTabData(
tabKey,
buildSewagePipeSearchParam(values),
values,
true,
false,
);
alert("제출되었습니다.");
}
break;
}
default:
alert("해당 탭 제출 API는 준비 중입니다.");
}
} catch {
alert("데이터 제출 중 오류가 발생했습니다.");
} finally {
hideGlobalProgress();
}
};
const cnacelSubmitFunc = async () => {
@@ -614,7 +1030,7 @@ const SewagePipeInp = () => {
sggNm: searchValues?.gugun ?? '',
statsYr: searchValues?.year ?? '',
mdfrId: userInfo.userUnqId,
statsCd: statsCd,
statsCd: tabStatsCdMap.get(activeTab) ?? statsCd,
};
const response = await statisticsApi.cancelSubmit(requestData);
@@ -841,6 +1257,232 @@ const SewagePipeInp = () => {
}
break;
}
case "ST00020003": {
const currentData = getPipeTypeGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert("저장할 데이터가 없습니다.");
return;
}
const columnFormatMap = new Map<string, string>();
PipeTypeStatusInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) {
columnFormatMap.set(col.fieldName, col.dataFormat);
}
});
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === "") return 0;
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const lastDataYear = getPipeTypeLastDataYear();
const saveList = currentData.map((d) => {
const yearlyStatList = [];
for (let year = 1995; year <= lastDataYear; year++) {
const wholKey = `y${year}PrlgWhol`;
const lyngKey = `y${year}LyngPrlg`;
const dmltnKey = `y${year}DmltnPrlg`;
yearlyStatList.push({
lyngYr: String(year),
lyngPrlg: formatNumber(
d[lyngKey] as string | number | undefined,
lyngKey,
),
dmltnPrlg: formatNumber(
d[dmltnKey] as string | number | undefined,
dmltnKey,
),
prlgWhol: formatNumber(
d[wholKey] as string | number | undefined,
wholKey,
),
});
}
return {
admdstCd: String(d.admdstCd ?? ""),
admdstNm: String(d.admdstNm ?? ""),
pipeSeCd: String(d.pipeSeCd ?? ""),
yearlyStatList,
rmrk: String(d.rmrk ?? ""),
lyngPrlgWhol: formatNumber(d.lyngPrlg, "lyngPrlg"),
dmltnPrlgWhol: formatNumber(d.dmltnPrlg, "dmltnPrlg"),
pipeByPrstWhol: formatNumber(d.pipeByPrstWhol, "pipeByPrstWhol"),
};
});
const userInfo = await getUserInfo();
const param: SeweragePipeInputPipeTypeStatusSaveRequest = {
statsYr: values.year ?? "",
ctpvNm: values.sido ?? "",
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
superAdmin: isAdminInfo ? "superAdmin" : "",
saveList,
};
const response = await statisticsInputSewPipeApiService.savePipeTypeStatus(param);
if (response.status === "success") {
setGlobalProgress(50);
const refreshedParam = buildSewagePipeSearchParam(values);
await loadSewagePipeActiveTabData(activeTab, refreshedParam, values, true, false);
setGlobalProgress(100);
alert("저장되었습니다.");
} else {
alert(response.message || "데이터 저장 중 오류가 발생했습니다.");
}
break;
}
case "ST00020004": {
const currentData = getPipeDiameterGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert("저장할 데이터가 없습니다.");
return;
}
const columnFormatMap = new Map<string, string>();
PipeDiameterStatusInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) {
columnFormatMap.set(col.fieldName, col.dataFormat);
}
});
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === "") return 0;
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const saveList = currentData.map((d) => ({
pipeSeCd: String(d.pipeSeCd ?? ""),
step1PdmtPrlg: formatNumber(d.step1PdmtPrlg, "step1PdmtPrlg"),
step2PdmtPrlg: formatNumber(d.step2PdmtPrlg, "step2PdmtPrlg"),
step3PdmtPrlg: formatNumber(d.step3PdmtPrlg, "step3PdmtPrlg"),
step4PdmtPrlg: formatNumber(d.step4PdmtPrlg, "step4PdmtPrlg"),
step5PdmtPrlg: formatNumber(d.step5PdmtPrlg, "step5PdmtPrlg"),
sqrePdmtPrlg: formatNumber(d.sqrePdmtPrlg, "sqrePdmtPrlg"),
etcPdmtPrlg: formatNumber(d.etcPdmtPrlg, "etcPdmtPrlg"),
rmrk: String(d.rmrk ?? ""),
pdmtByTsmWhol: formatNumber(d.pdmtByTsmWhol, "pdmtByTsmWhol"),
admdstCd: String(d.admdstCd ?? ""),
admdstNm: String(d.admdstNm ?? ""),
}));
const userInfo = await getUserInfo();
const param: SeweragePipeInputPipeDiameterStatusSaveRequest = {
statsYr: values.year ?? "",
ctpvNm: values.sido ?? "",
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
superAdmin: isAdminInfo ? "superAdmin" : "",
saveList,
};
const response = await statisticsInputSewPipeApiService.savePipeDiameterStatus(param);
if (response.status === "success") {
setGlobalProgress(50);
const refreshedParam = buildSewagePipeSearchParam(values);
await loadSewagePipeActiveTabData(activeTab, refreshedParam, values, true, false);
setGlobalProgress(100);
alert("저장되었습니다.");
} else {
alert(response.message || "데이터 저장 중 오류가 발생했습니다.");
}
break;
}
case "ST00020005": {
const currentData = getManholeGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert("저장할 데이터가 없습니다.");
return;
}
const columnFormatMap = new Map<string, string>();
ManholeStatusInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) {
columnFormatMap.set(col.fieldName, col.dataFormat);
}
});
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === "") return 0;
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const minYy = 14;
const maxYy = getManholeLastDataYear() % 100;
const saveList = currentData.map((d) => {
const yearlyStats = [];
for (let yy = minYy; yy <= maxYy; yy++) {
const year = 2000 + yy;
const wholKey = `yy${yy}WholMnhlCnt`;
const step1Key = `yy${yy}Step1MnhlCnt`;
const step2Key = `yy${yy}Step2MnhlCnt`;
const step3Key = `yy${yy}Step3MnhlCnt`;
const step4Key = `yy${yy}Step4MnhlCnt`;
const step5Key = `yy${yy}Step5MnhlCnt`;
yearlyStats.push({
manhYr: String(year),
wholMnhlCnt: formatNumber(d[wholKey] as string | number | undefined, wholKey),
step1MnhlCnt: formatNumber(d[step1Key] as string | number | undefined, step1Key),
step2MnhlCnt: formatNumber(d[step2Key] as string | number | undefined, step2Key),
step3MnhlCnt: formatNumber(d[step3Key] as string | number | undefined, step3Key),
step4MnhlCnt: formatNumber(d[step4Key] as string | number | undefined, step4Key),
step5MnhlCnt: formatNumber(d[step5Key] as string | number | undefined, step5Key),
});
}
return {
emphsMngSeCd: String(d.emphsMngSeCd ?? ""),
admdstCd: String(d.admdstCd ?? ""),
admdstNm: String(d.admdstNm ?? ""),
yearlyStats,
rmrk: String(d.rmrk ?? ""),
};
});
const userInfo = await getUserInfo();
const param: SeweragePipeInputManholeStatusSaveRequest = {
statsYr: values.year ?? "",
ctpvNm: values.sido ?? "",
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
superAdmin: isAdminInfo ? "superAdmin" : "",
saveList,
};
const response = await statisticsInputSewPipeApiService.saveManholeStatus(param);
if (response.status === "success") {
setGlobalProgress(50);
const refreshedParam = buildSewagePipeSearchParam(values);
await loadSewagePipeActiveTabData(activeTab, refreshedParam, values, true, false);
setGlobalProgress(100);
alert("저장되었습니다.");
} else {
alert(response.message || "데이터 저장 중 오류가 발생했습니다.");
}
break;
}
default:
alert("현재 탭 저장 API 연동 전입니다.");
break;
@@ -896,7 +1538,7 @@ const SewagePipeInp = () => {
{showEnterableToolbarButtons && (
<>
<LastYearDataButton getLastYearDataFunc={() => void getLastYearDataFunc()} />
<SubmitButton submitFunc={submitFunc} />
<SubmitButton submitFunc={() => void submitFunc(false)} />
</>
)}
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
@@ -1051,11 +1693,17 @@ const SewagePipeInp = () => {
enableSort={false}
statusInfo={tabStatusInfoMap.get(tabKey) ?? null}
getGridDataRef={
tabKey === "ST00020001"
tabKey === REGIONAL_FACT_TAB_KEY
? getGridDataRef
: tabKey === "ST00020002"
? getFactInstallGridDataRef
: undefined
: tabKey === "ST00020003"
? getPipeTypeGridDataRef
: tabKey === "ST00020004"
? getPipeDiameterGridDataRef
: tabKey === "ST00020005"
? getManholeGridDataRef
: undefined
}
cellStyleCallback={({ row, fieldName }) => {
const verification = (row as GridRowWithVerification).verificationResult;
@@ -1107,6 +1755,36 @@ const SewagePipeInp = () => {
: null
}
/>
<SubmitDailog
open={showDailog}
onOpenChange={(open) => {
setShowDailog(open);
if (!open) setIsExceptionSubmitFlow(false);
}}
statusInfo={tabStatusInfoMap.get(activeTab) ?? null}
onConfirm={executeSubmit}
onExceptionConfirm={executeExceptionSubmit}
submitMode={isExceptionSubmitFlow ? "exception" : "normal"}
isLoading={isLoading}
/>
<VerificationResultDialog
open={verificationResultDialogOpen}
onOpenChange={setVerificationResultDialogOpen}
queryContext={
searchValues?.year &&
searchValues?.sido &&
searchValues?.bsnmCd &&
(tabStatsCdMap.get(activeTab) ?? statsCd)
? {
sewerBzmnCd: searchValues.bsnmCd,
ctpvNm: searchValues.sido,
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
statsYr: searchValues.year,
statsCd: tabStatsCdMap.get(activeTab) ?? statsCd,
}
: null
}
/>
</>
);
};
@@ -35,13 +35,13 @@ import {
type SewerageSupplyRateSaveItem,
type SewerageSupplyRateVerificationResult,
type SewerageSupplyRateSaveRequest,
type SewerageSupplyRateStatusInfo,
getVerificationPeriodActionBlockedMessage,
isInputPeriodCurrent,
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
import type {
cancelSubmitRequest,
ImrStatusResponse,
SewerageStatusInfo,
SubmitExceptionRequest,
} from "@/entities/statistics/model/statistics-interface";
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
@@ -108,7 +108,7 @@ const SeweragePenetrationRateInp = () => {
const [isEnterable, setIsEnterable] = useState<boolean>(true);
const [lastSearchedYear, setLastSearchedYear] = useState<string | null>(null);
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
const [statusInfo, setStatusInfo] = useState<SewerageSupplyRateStatusInfo | null>(null);
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
/** 마지막 조회 시 check-input-period 응답 (검증기간 여부 판단) */
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
const [showDailog, setShowDailog] = useState<boolean>(false);
@@ -121,6 +121,7 @@ const SeweragePenetrationRateInp = () => {
const { confirm } = useConfirm();
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);
@@ -131,6 +132,7 @@ const SeweragePenetrationRateInp = () => {
const activeTabId = useTabStore((s) => s.activeTabId);
const inputFocusNonce = useStatisticsInputFocusStore((s) => s.nonce);
useLayoutEffect(() => {
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE) {
return;
@@ -364,7 +366,7 @@ const SeweragePenetrationRateInp = () => {
return;
}
try {
showGlobalProgress({ label: "제출 진행 중...", progress: 0 });
showGlobalProgress({ label: '서버에 요청 중...', progress: 0 });
const userInfo = await getUserInfo();
const currentData = getGridDataRef.current?.();
@@ -439,11 +441,13 @@ const SeweragePenetrationRateInp = () => {
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
};
setGlobalProgressLabel('일반 검증 진행 중...');
const response = await statisticsInputSewSupplyRateApi.submitSewerageSupplyRate(requestData);
if (response.status === 'success') {
setGlobalProgress(50);
if (response.data.finalStatus === '5') {
setGlobalProgressLabel('IMR 검증 진행 중...');
const imrParam = {
user_id: userInfo.userUnqId,
sewer_bzmn_cd: searchValues?.bsnmCd ?? '',
@@ -4,7 +4,7 @@ import { useEffect } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { statisticsInputSubmitInfoSchema, type StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
import { type SewerageSupplyRateStatusInfo } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
import type { SewerageStatusInfo } from "@/entities/statistics/model/statistics-interface";
import {
Button,
@@ -44,7 +44,7 @@ const PHONE_FIELDS: (keyof StatisticsInputSubmitInfoFormData)[] = ["laePicTelNo"
interface SubmitDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
statusInfo: SewerageSupplyRateStatusInfo | null;
statusInfo: SewerageStatusInfo | null;
/** 다이얼로그에서 저장 클릭 시 호출. 제출 정보를 넘기면 부모에서 requestData에 포함해 제출 API 호출 */
onConfirm?: (data: StatisticsInputSubmitInfoFormData) => void | Promise<void>;
/** 예외 제출로 다이얼로그를 연 경우: 일반 제출(onConfirm) 대신 호출 */
@@ -65,7 +65,7 @@ const defaultValues: StatisticsInputSubmitInfoFormData = {
laeIdfrTelNo: "",
};
function statusInfoToFormData(info: SewerageSupplyRateStatusInfo): StatisticsInputSubmitInfoFormData {
function statusInfoToFormData(info: SewerageStatusInfo): StatisticsInputSubmitInfoFormData {
return {
laePicNm: info.laePicNm ?? "",
laeKbrdrJbgdNm: info.laeKbrdrJbgdNm ?? "",
@@ -1,6 +1,6 @@
import { useState } from "react";
import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import type { ColumnConfig } from "@/components/type/DataGrid.type";
@@ -1,6 +1,6 @@
import { useState } from "react";
import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import type { ColumnConfig } from "@/components/type/DataGrid.type";
@@ -1,6 +1,6 @@
import { useState } from "react";
import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import type { ColumnConfig } from "@/components/type/DataGrid.type";
+19 -1
View File
@@ -11,6 +11,7 @@ export type DataGridStatusBannerInfo = {
laeSbmsnYmd: string | null;
laePicNm: string | null;
laePicTelNo: string | null;
rjctOpnnCn: string | null;
};
/** setColumns에 전달하는 컬럼 정보 (footer 배열/단일 객체 포함) */
export type RealGridColumnInfo = {
@@ -115,7 +116,24 @@ export type DataFieldValueCallback = (
/** 계산 필드 설정 (computedFieldCallbacks.ts 참고) */
export type ComputedFieldConfig =
| { fields: string[]; type: "sum" | "avg" }
| { fields: string[]; type: "sum" | "avg" | "max" | "difference" }
| {
minuendFields: string[];
subtrahendFields: string[];
type: "differenceOfSums";
}
| {
baseField: string;
sumFields: string[];
type: "maxOfFieldAndSum";
}
| {
groups: Array<{
baseField: string;
sumFields: string[];
}>;
type: "sumOfMaxOfFieldAndSumGroups";
}
| {
numeratorFields: string[];
denominatorFields: string[];
@@ -16,25 +16,88 @@ export const FactInstallStatusInpColumn = {
{ name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '하수처리시설용량(m³/일)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 처리구역면적(km²)
{ name: 'prcsZoneArea', fieldName: 'prcsZoneArea', header: { text: '합계' }, footer: footerDecimal3, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{
name: 'prcsZoneArea',
fieldName: 'prcsZoneArea',
header: { text: '합계' },
footer: footerDecimal3,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.000',
computed: { fields: ['cbsysPrcsZoneArea', 'sprsysPrcsZoneArea'], type: 'sum' as const },
},
{ name: 'cbsysPrcsZoneArea', fieldName: 'cbsysPrcsZoneArea', header: { text: '합류식' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'sprsysPrcsZoneArea', fieldName: 'sprsysPrcsZoneArea', header: { text: '분류식' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
// 분류식화율
{ name: 'sprsysVartnRt', fieldName: 'sprsysVartnRt', header: { text: '분류식화율' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{
name: 'sprsysVartnRt',
fieldName: 'sprsysVartnRt',
header: { text: '분류식화율' },
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.0',
computed: {
numeratorFields: ['sprsysPrcsZoneArea'],
denominatorFields: ['cbsysPrcsZoneArea', 'sprsysPrcsZoneArea'],
type: 'pblswrgPopltnSuprtRate' as const,
},
},
// 배수설비(개소)
{ name: 'mltvEqpmCnt', fieldName: 'mltvEqpmCnt', header: { text: '합계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{
name: 'mltvEqpmCnt',
fieldName: 'mltvEqpmCnt',
header: { text: '합계' },
footer: footerInt,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0',
computed: { fields: ['sprsysMltvLnkgCnt', 'sprsysMltvYetLnkgCnt', 'cbsysMltvEqpmCnt'], type: 'sum' as const },
},
{ name: 'cbsysMltvEqpmCnt', fieldName: 'cbsysMltvEqpmCnt', header: { text: '합류식' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sprsysMltvEqpmCnt', fieldName: 'sprsysMltvEqpmCnt', header: { text: '소계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{
name: 'sprsysMltvEqpmCnt',
fieldName: 'sprsysMltvEqpmCnt',
header: { text: '소계' },
footer: footerInt,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0',
computed: { fields: ['sprsysMltvLnkgCnt', 'sprsysMltvYetLnkgCnt'], type: 'sum' as const },
},
{ name: 'sprsysMltvLnkgCnt', fieldName: 'sprsysMltvLnkgCnt', header: { text: '연결' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sprsysMltvYetLnkgCnt', fieldName: 'sprsysMltvYetLnkgCnt', header: { text: '미연결' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수관로 > 총시설연장
{ name: 'wholFcltPrlg', fieldName: 'wholFcltPrlg', header: { text: '총시설연장(m)' }, footer: footerDecimal2, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{
name: 'wholFcltPrlg',
fieldName: 'wholFcltPrlg',
header: { text: '총시설연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: { fields: ['icptgPrlg', 'atlfcsPrlg', 'cbsysBrchlnPrlg', 'exlnBrchlnPrlg', 'drtwBrchlnPrlg'], type: 'sum' as const },
},
// 하수관로 > 차집/오수간선(m)
{ name: 'wholIcptgAtlfcsPrlg', fieldName: 'wholIcptgAtlfcsPrlg', header: { text: '합계' }, footer: footerDecimal2, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{
name: 'wholIcptgAtlfcsPrlg',
fieldName: 'wholIcptgAtlfcsPrlg',
header: { text: '합계' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: { fields: ['icptgPrlg', 'atlfcsPrlg'], type: 'sum' as const },
},
{ name: 'icptgPrlg', fieldName: 'icptgPrlg', header: { text: '차집' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'atlfcsPrlg', fieldName: 'atlfcsPrlg', header: { text: '오수 간선' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
@@ -46,7 +109,17 @@ export const FactInstallStatusInpColumn = {
{ name: 'sprsysExlnOtflCnt', fieldName: 'sprsysExlnOtflCnt', header: { text: '우수토구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수관로 > 지선(m)
{ name: 'wholBrchlnPrlg', fieldName: 'wholBrchlnPrlg', header: { text: '합계' }, footer: footerDecimal2, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{
name: 'wholBrchlnPrlg',
fieldName: 'wholBrchlnPrlg',
header: { text: '합계' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: { fields: ['cbsysBrchlnPrlg', 'exlnBrchlnPrlg', 'drtwBrchlnPrlg'], type: 'sum' as const },
},
{ name: 'cbsysBrchlnPrlg', fieldName: 'cbsysBrchlnPrlg', header: { text: '합류식' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
// 하수관로 > 지선(m) > 분류식
@@ -4,10 +4,12 @@ import { ValueType } from "realgrid";
// 하수도 통계 관리 > 02.하수관로 > tab 5. 맨홀 현황
// SeweragePipeInputManholeStatusItem + commonInfo.ctpvNm (그리드 행에 병합)
// API: yy14=2014년 … yy25=2025년 (필드 접두 yy + 연도 끝자리)
// API: yy14=2014년 … yy{(현재연도-1)%100} (필드 접두 yy + 연도 끝자리)
const MANHOLE_YY_MIN = 14;
const MANHOLE_YY_MAX = 25;
const MANHOLE_START_YEAR = 2014;
const MANHOLE_LAST_DATA_YEAR = Math.max(MANHOLE_START_YEAR, new Date().getFullYear() - 1);
const MANHOLE_YY_MIN = MANHOLE_START_YEAR % 100;
const MANHOLE_YY_MAX = MANHOLE_LAST_DATA_YEAR % 100;
const calendarYearFromYy = (yy: number) => 2000 + yy;
const intCol = (name: string, headerText: string): ColumnConfig => ({
@@ -15,26 +17,73 @@ const intCol = (name: string, headerText: string): ColumnConfig => ({
fieldName: name,
header: { text: headerText },
footer: footerInt,
editable: false,
// editable: false,
valueType: ValueType.NUMBER,
dataFormat: "#,##0",
});
const yearlySumFields = (suffix: "WholMnhlCnt" | "Step1MnhlCnt" | "Step2MnhlCnt" | "Step3MnhlCnt" | "Step4MnhlCnt" | "Step5MnhlCnt") =>
Array.from({ length: MANHOLE_YY_MAX - MANHOLE_YY_MIN + 1 }, (_, i) => `yy${MANHOLE_YY_MIN + i}${suffix}`);
function totalBlockColumns(): ColumnConfig[] {
return [
intCol("totalWholMnhlCnt", "계"),
intCol("totalStep1MnhlCnt", "주철"),
intCol("totalStep2MnhlCnt", "주철(조화)"),
intCol("totalStep3MnhlCnt", "콘크리트"),
intCol("totalStep4MnhlCnt", "기타"),
intCol("totalStep5MnhlCnt", "추락방지시설설치맨홀(개)"),
{
...intCol("totalWholMnhlCnt", "계"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("WholMnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep1MnhlCnt", "주철"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step1MnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep2MnhlCnt", "주철(조화)"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step2MnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep3MnhlCnt", "콘크리트"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step3MnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep4MnhlCnt", "기타"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step4MnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep5MnhlCnt", "추락방지시설설치맨홀(개)"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step5MnhlCnt"), type: "sum" as const },
},
];
}
function yyBlockColumns(yy: number): ColumnConfig[] {
const p = `yy${yy}`;
return [
intCol(`${p}WholMnhlCnt`, "계"),
{
...intCol(`${p}WholMnhlCnt`, "계"),
styleName: "rg-cell-total",
editable: false,
computed: {
fields: [
`${p}Step1MnhlCnt`,
`${p}Step2MnhlCnt`,
`${p}Step3MnhlCnt`,
`${p}Step4MnhlCnt`,
`${p}Step5MnhlCnt`,
],
type: "sum" as const,
},
},
intCol(`${p}Step1MnhlCnt`, "주철"),
intCol(`${p}Step2MnhlCnt`, "주철(조화)"),
intCol(`${p}Step3MnhlCnt`, "콘크리트"),
@@ -101,8 +150,10 @@ export const ManholeStatusInpColumn = {
name: "emphsMngSeNm",
fieldName: "emphsMngSeNm",
header: { text: "집중강우중점관리구역구분" },
footer: { text: "합계" },
editable: false,
styleName: "rg-cell-readonly",
footer: { text: "합계" },
// editable: false,
},
...totalBlockColumns(),
...Array.from({ length: MANHOLE_YY_MAX - MANHOLE_YY_MIN + 1 }, (_, i) =>
@@ -117,6 +168,14 @@ export const ManholeStatusInpColumn = {
editable: false,
styleName: "rg-cell-readonly",
},
{
name: "emphsMngSeCd",
fieldName: "emphsMngSeCd",
header: { text: "집중강우중점관리구역코드" },
visible: false,
editable: false,
styleName: "rg-cell-readonly",
},
] as ColumnConfig[],
columnLayout: [
{
@@ -149,7 +208,8 @@ export const ManholeStatusInpColumn = {
},
...yearLayoutItems,
"rmrk",
"admdstCd"
"admdstCd",
"emphsMngSeCd",
] as ColumnLayoutItem[],
rowStartNum: 3,
fixedColumns: 2,
@@ -40,8 +40,10 @@ export const PipeDiameterStatusInpColumn = {
header: { text: "총계(m)" },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
computed: { fields: ['step1PdmtPrlg', 'step2PdmtPrlg', 'step3PdmtPrlg', 'step4PdmtPrlg', 'step5PdmtPrlg', 'sqrePdmtPrlg', 'etcPdmtPrlg'], type: 'sum' as const },
},
{
name: "step1PdmtPrlg",
@@ -5,23 +5,24 @@ import { ValueType } from "realgrid";
// 하수도 통계 관리 > 02.하수관로 > tab 3. 관종별 현황
// SeweragePipeInputPipeTypeStatusItem + commonInfo.ctpvNm (그리드 행에 병합)
//
// 연도 그룹: yy1 = 1996년 이전, yy2 = 1996년 … API 상한 yy30(2024년)까지.
// 표시 구간: 1996년 ~ (현재 연도 − 1) 까지. API 밖 연도는 yy 인덱스 상한으로 캡.
const YY_API_MAX = 30;
/** yy 인덱스 → 달력 연도 (yy2 → 1996) */
const calendarYearForYyIndex = (yyIndex: number) => 1994 + yyIndex;
// 연도 그룹: y1995 = 1996년 이전, y1996 = 1996년 …
// 표시 구간: 1996년 ~ (현재 연도 − 1) 까지.
/** 이 모듈이 로드될 때의 “데이터 기준 마지막 연도” (현재 연도 − 1) */
const lastDataYear = new Date().getFullYear() - 1;
const firstGroupedYear = 1995;
const firstVisibleYear = 1996;
const yyYearCols = (
n: number,
const yearField = (year: number, suffix: "PrlgWhol" | "LyngPrlg" | "DmltnPrlg") =>
`y${year}${suffix}`;
const yearCols = (
year: number,
opts: { footer?: typeof footerDecimal2; visible?: boolean } = {}
): ColumnConfig[] => {
const whol = `yy${n}PrlgWhol`;
const lyng = `yy${n}LyngPrlg`;
const dmltn = `yy${n}DmltnPrlg`;
const whol = yearField(year, "PrlgWhol");
const lyng = yearField(year, "LyngPrlg");
const dmltn = yearField(year, "DmltnPrlg");
const vis = opts.visible === false ? ({ visible: false } as const) : {};
return [
{
@@ -30,8 +31,10 @@ const yyYearCols = (
header: { text: "계" },
...(opts.footer !== undefined ? { footer: opts.footer } : {}),
editable: false,
styleName: "rg-cell-total",
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
computed: { fields: [lyng, dmltn], type: "difference" as const },
...vis,
},
{
@@ -39,6 +42,8 @@ const yyYearCols = (
fieldName: lyng,
header: { text: "매설연장(m)" },
...(opts.footer !== undefined ? { footer: opts.footer } : {}),
editable: false,
styleName: "rg-cell-readonly",
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
...vis,
@@ -55,18 +60,25 @@ const yyYearCols = (
];
};
/** yy2(1996) ~ yyK 까지 표시. K는 API 상한 YY_API_MAX */
const maxYyIndex =
lastDataYear < 1996 ? 1 : Math.min(YY_API_MAX, lastDataYear - 1994);
const visibleYears =
lastDataYear < firstVisibleYear
? []
: Array.from(
{ length: lastDataYear - firstVisibleYear + 1 },
(_, i) => firstVisibleYear + i,
);
const yearLayoutItems: ColumnLayoutItem[] = [];
for (let yy = 2; yy <= maxYyIndex; yy++) {
const y = calendarYearForYyIndex(yy);
for (const y of visibleYears) {
yearLayoutItems.push({
name: `year${y}`,
hideChildHeaders: false,
header: { text: `${y}년` },
items: [`yy${yy}PrlgWhol`, `yy${yy}LyngPrlg`, `yy${yy}DmltnPrlg`],
items: [
yearField(y, "PrlgWhol"),
yearField(y, "LyngPrlg"),
yearField(y, "DmltnPrlg"),
],
});
}
@@ -105,15 +117,28 @@ export const PipeTypeStatusInpColumn = {
name: "pipeByPrstWhol",
fieldName: "pipeByPrstWhol",
header: { text: "계" },
styleName: "rg-cell-total",
footer: footerDecimal2,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
computed: {
minuendFields: [
yearField(firstGroupedYear, "LyngPrlg"),
...visibleYears.map((year) => yearField(year, "LyngPrlg")),
],
subtrahendFields: [
yearField(firstGroupedYear, "DmltnPrlg"),
...visibleYears.map((year) => yearField(year, "DmltnPrlg")),
],
type: "differenceOfSums" as const,
},
},
{
name: "lyngPrlg",
fieldName: "lyngPrlg",
header: { text: "매설연장(m)" },
styleName: "rg-cell-total",
footer: footerDecimal2,
editable: false,
valueType: ValueType.NUMBER,
@@ -123,19 +148,25 @@ export const PipeTypeStatusInpColumn = {
name: "dmltnPrlg",
fieldName: "dmltnPrlg",
header: { text: "철거연장(m)" },
styleName: "rg-cell-total",
footer: footerDecimal2,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: "#,##0.00",
computed: {
fields: [
yearField(firstGroupedYear, "DmltnPrlg"),
...visibleYears.map((year) => yearField(year, "DmltnPrlg")),
],
type: "sum" as const,
},
},
...yyYearCols(1, {}),
...Array.from({ length: YY_API_MAX - 1 }, (_, i) => {
const yy = i + 2;
return yyYearCols(yy, {
...yearCols(firstGroupedYear, { footer: footerDecimal2 }),
...visibleYears.map((year) =>
yearCols(year, {
footer: footerDecimal2,
visible: yy <= maxYyIndex,
});
}).flat(),
}),
).flat(),
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
{
name: "admdstCd",
@@ -173,7 +204,11 @@ export const PipeTypeStatusInpColumn = {
name: "year1996Before",
hideChildHeaders: false,
header: { text: "1996년 이전" },
items: ["yy1PrlgWhol", "yy1LyngPrlg", "yy1DmltnPrlg"],
items: [
yearField(firstGroupedYear, "PrlgWhol"),
yearField(firstGroupedYear, "LyngPrlg"),
yearField(firstGroupedYear, "DmltnPrlg"),
],
},
...yearLayoutItems,
"rmrk",
@@ -9,19 +9,103 @@ export const RegionalFactStatusInpColumn = {
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' },
// 기본 정보
{ name: 'goalYr', fieldName: 'goalYr', header: { text: '하수정비기본계획목표년도' }},
{ name: 'wholPplMtncPlanPrlg', fieldName: 'wholPplMtncPlanPrlg', header: { text: '총관로정비계획연장(m)' }, footer: footerDecimal2, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'wholPlanPrlg', fieldName: 'wholPlanPrlg', header: { text: '총계획연장(m)' }, footer: footerDecimal2, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'wholFcltPrlg', fieldName: 'wholFcltPrlg', header: { text: '총시설연장(m)' }, footer: footerDecimal2, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'swagePplPsplyRt', fieldName: 'swagePplPsplyRt', header: { text: '하수관로보급률(%)' }, footer: footerDecimal1, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'goalYr', fieldName: 'goalYr', header: { text: '하수정비기본계획목표년도' } },
{
name: 'wholPplMtncPlanPrlg',
fieldName: 'wholPplMtncPlanPrlg',
header: { text: '총관로정비계획연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: { fields: ['cbsysPplMtncPlanPrlg', 'drtwPplMtncPlanPrlg', 'exlnPplMtncPlanPrlg'], type: 'sum' as const },
},
{
name: 'wholPlanPrlg',
fieldName: 'wholPlanPrlg',
header: { text: '총계획연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: {
groups: [
{
baseField: 'cbsysPplMtncPlanPrlg',
sumFields: ['cbsysSqrePrlg', 'cbsysCiclPrlg', 'cbsysOpnPrlg', 'cbsysGttrPrlg'],
},
{
baseField: 'drtwPplMtncPlanPrlg',
sumFields: ['drtwPplSqrePrlg', 'drtwPplCiclPrlg'],
},
{
baseField: 'exlnPplMtncPlanPrlg',
sumFields: ['exlnPplSqrePrlg', 'exlnPplCiclPrlg', 'exlnPplOpnPrlg', 'exlnPplGttrPrlg'],
},
],
type: 'sumOfMaxOfFieldAndSumGroups' as const,
},
},
{
name: 'wholFcltPrlg',
fieldName: 'wholFcltPrlg',
header: { text: '총시설연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: { fields: ['cbsysSqrePrlg', 'cbsysCiclPrlg', 'cbsysOpnPrlg', 'cbsysGttrPrlg', 'drtwPplSqrePrlg', 'drtwPplCiclPrlg', 'exlnPplSqrePrlg', 'exlnPplCiclPrlg', 'exlnPplOpnPrlg', 'exlnPplGttrPrlg'], type: 'sum' as const },
},
{
name: 'swagePplPsplyRt',
fieldName: 'swagePplPsplyRt',
header: { text: '하수관로보급률(%)' },
footer: footerDecimal1,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.0',
computed: {
numeratorFields: ['wholFcltPrlg'],
denominatorFields: ['wholPlanPrlg'],
type: 'pblswrgPopltnSuprtRate' as const,
},
},
// 합류식(m)
{ name: 'cbsysPplMtncPlanArea', fieldName: 'cbsysPplMtncPlanArea', header: { text: '관로정비계획면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'cbsysPplMtncPlanPrlg', fieldName: 'cbsysPplMtncPlanPrlg', header: { text: '관로정비계획연장(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'cbsysPlanPrlg', fieldName: 'cbsysPlanPrlg', header: { text: '계획연장(m)' }, footer: footerDecimal2, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'cbsysFcltPrlg', fieldName: 'cbsysFcltPrlg', header: { text: '시설연장(m)' }, footer: footerDecimal2, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{
name: 'cbsysPlanPrlg',
fieldName: 'cbsysPlanPrlg',
header: { text: '계획연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: {
baseField: 'cbsysPplMtncPlanPrlg',
sumFields: ['cbsysSqrePrlg', 'cbsysCiclPrlg', 'cbsysOpnPrlg', 'cbsysGttrPrlg'],
type: 'maxOfFieldAndSum' as const,
},
},
{
name: 'cbsysFcltPrlg',
fieldName: 'cbsysFcltPrlg',
header: { text: '시설연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: { fields: ['cbsysSqrePrlg', 'cbsysCiclPrlg', 'cbsysOpnPrlg', 'cbsysGttrPrlg'], type: 'sum' as const },
},
{ name: 'cbsysSqrePrlg', fieldName: 'cbsysSqrePrlg', header: { text: '사각형(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'cbsysCiclPrlg', fieldName: 'cbsysCiclPrlg', header: { text: '원형(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'cbsysOpnPrlg', fieldName: 'cbsysOpnPrlg', header: { text: '개거(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
@@ -30,16 +114,64 @@ export const RegionalFactStatusInpColumn = {
// 분류식(m) > 오수관로
{ name: 'drtwPplMtncPlanArea', fieldName: 'drtwPplMtncPlanArea', header: { text: '관로정비계획면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'drtwPplMtncPlanPrlg', fieldName: 'drtwPplMtncPlanPrlg', header: { text: '관로정비계획연장(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'drtwPplPlanPrlg', fieldName: 'drtwPplPlanPrlg', header: { text: '계획연장(m)' }, footer: footerDecimal2, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'drtwPplFcltPrlg', fieldName: 'drtwPplFcltPrlg', header: { text: '시설연장(m)' }, footer: footerDecimal2, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{
name: 'drtwPplPlanPrlg',
fieldName: 'drtwPplPlanPrlg',
header: { text: '계획연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: {
baseField: 'drtwPplMtncPlanPrlg',
sumFields: ['drtwPplSqrePrlg', 'drtwPplCiclPrlg'],
type: 'maxOfFieldAndSum' as const,
},
},
{
name: 'drtwPplFcltPrlg',
fieldName: 'drtwPplFcltPrlg',
header: { text: '시설연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: { fields: ['drtwPplSqrePrlg', 'drtwPplCiclPrlg'], type: 'sum' as const },
},
{ name: 'drtwPplSqrePrlg', fieldName: 'drtwPplSqrePrlg', header: { text: '사각형(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'drtwPplCiclPrlg', fieldName: 'drtwPplCiclPrlg', header: { text: '원형(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
// 분류식(m) > 우수관로
{ name: 'exlnPplMtncPlanArea', fieldName: 'exlnPplMtncPlanArea', header: { text: '관로정비계획면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'exlnPplMtncPlanPrlg', fieldName: 'exlnPplMtncPlanPrlg', header: { text: '관로정비계획연장(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'exlnPplPlanPrlg', fieldName: 'exlnPplPlanPrlg', header: { text: '계획연장(m)' }, footer: footerDecimal2, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'exlnPplFcltPrlg', fieldName: 'exlnPplFcltPrlg', header: { text: '시설연장(m)' }, footer: footerDecimal2, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{
name: 'exlnPplPlanPrlg',
fieldName: 'exlnPplPlanPrlg',
header: { text: '계획연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: {
baseField: 'exlnPplMtncPlanPrlg',
sumFields: ['exlnPplSqrePrlg', 'exlnPplCiclPrlg', 'exlnPplOpnPrlg', 'exlnPplGttrPrlg'],
type: 'maxOfFieldAndSum' as const,
},
},
{
name: 'exlnPplFcltPrlg',
fieldName: 'exlnPplFcltPrlg',
header: { text: '시설연장(m)' },
footer: footerDecimal2,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.00',
computed: { fields: ['exlnPplSqrePrlg', 'exlnPplCiclPrlg', 'exlnPplOpnPrlg', 'exlnPplGttrPrlg'], type: 'sum' as const },
},
{ name: 'exlnPplSqrePrlg', fieldName: 'exlnPplSqrePrlg', header: { text: '사각형(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'exlnPplCiclPrlg', fieldName: 'exlnPplCiclPrlg', header: { text: '원형(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
{ name: 'exlnPplOpnPrlg', fieldName: 'exlnPplOpnPrlg', header: { text: '개거(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
@@ -51,7 +183,7 @@ export const RegionalFactStatusInpColumn = {
// 분류식 > 오수맨홀, 우수맨홀
{ name: 'sprsysDrtwMnhlCnt', fieldName: 'sprsysDrtwMnhlCnt', header: { text: '오수맨홀(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sprsysExlnMnhlCnt', fieldName: 'sprsysExlnMnhlCnt', header: { text: '우수맨홀(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'drtwRcvprpCnt', fieldName: 'drtwRcvprpCnt', header: { text: '오수받이(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'exlnRcvprpCnt', fieldName: 'exlnRcvprpCnt', header: { text: '빗물받이(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'stofcbrCnt', fieldName: 'stofcbrCnt', header: { text: '우수토실(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
@@ -59,7 +191,21 @@ export const RegionalFactStatusInpColumn = {
// 하수도대장 전산화
{ name: 'sewerLdgrEdpPrlg', fieldName: 'sewerLdgrEdpPrlg', header: { text: '하수도대장전산화연장(m)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sewerLdgrEdpRt', fieldName: 'sewerLdgrEdpRt', header: { text: '하수도대장전산화율(%)' }, footer: footerDecimal1, editable: false, styleName: 'rg-cell-readonly', valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{
name: 'sewerLdgrEdpRt',
fieldName: 'sewerLdgrEdpRt',
header: { text: '하수도대장전산화율(%)' },
footer: footerDecimal1,
editable: false,
styleName: 'rg-cell-total',
valueType: ValueType.NUMBER,
dataFormat: '#,##0.0',
computed: {
numeratorFields: ['sewerLdgrEdpPrlg'],
denominatorFields: ['cbsysPplMtncPlanPrlg', 'drtwPplMtncPlanPrlg', 'exlnPplMtncPlanPrlg'],
type: 'pblswrgPopltnSuprtRate' as const,
},
},
// 비고
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } },
@@ -37,6 +37,90 @@ export function createAvgValueCallback(fieldNames: string[]): DataFieldValueCall
};
}
/** 차이값: fields[0] − fields[1] (예: 매설연장 − 철거연장) */
export function createDifferenceValueCallback(fields: string[]): DataFieldValueCallback {
const a = fields[0] ?? "";
const b = fields[1] ?? "";
return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => {
if (!a || !b) return 0;
const ia = fieldNamesArr.indexOf(a);
const ib = fieldNamesArr.indexOf(b);
const va = ia >= 0 ? toNum(values[ia]) : 0;
const vb = ib >= 0 ? toNum(values[ib]) : 0;
return va - vb;
};
}
/** (여러 필드 합) - (여러 필드 합) */
export function createDifferenceOfSumsValueCallback(
minuendFields: string[],
subtrahendFields: string[],
): DataFieldValueCallback {
return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => {
const sumOf = (fields: string[]) => {
let sum = 0;
for (const f of fields) {
const idx = fieldNamesArr.indexOf(f);
if (idx >= 0) sum += toNum(values[idx]);
}
return sum;
};
return sumOf(minuendFields) - sumOf(subtrahendFields);
};
}
/** 최대값용 valueCallback 생성 */
export function createMaxValueCallback(fieldNames: string[]): DataFieldValueCallback {
return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => {
let max = Number.NEGATIVE_INFINITY;
for (const f of fieldNames) {
const idx = fieldNamesArr.indexOf(f);
if (idx >= 0) {
const val = toNum(values[idx]);
max = Math.max(max, val);
}
}
return max === Number.NEGATIVE_INFINITY ? 0 : max;
};
}
/** 단일 필드값 vs 여러 필드 합계 중 최대값 */
export function createMaxOfFieldAndSumValueCallback(
baseField: string,
sumFields: string[],
): DataFieldValueCallback {
return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => {
const baseIdx = fieldNamesArr.indexOf(baseField);
const baseVal = baseIdx >= 0 ? toNum(values[baseIdx]) : 0;
let sumVal = 0;
for (const f of sumFields) {
const idx = fieldNamesArr.indexOf(f);
if (idx >= 0) sumVal += toNum(values[idx]);
}
return Math.max(baseVal, sumVal);
};
}
/** 여러 그룹의 max(baseField, sum(sumFields)) 값을 모두 합산 */
export function createSumOfMaxOfFieldAndSumGroupsValueCallback(
groups: Array<{ baseField: string; sumFields: string[] }>,
): DataFieldValueCallback {
return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => {
let total = 0;
for (const group of groups) {
const baseIdx = fieldNamesArr.indexOf(group.baseField);
const baseVal = baseIdx >= 0 ? toNum(values[baseIdx]) : 0;
let sumVal = 0;
for (const f of group.sumFields) {
const idx = fieldNamesArr.indexOf(f);
if (idx >= 0) sumVal += toNum(values[idx]);
}
total += Math.max(baseVal, sumVal);
}
return total;
};
}
// 공공하수처리구역인구보급률(%), 고도처리인구보급률(%) valueCallback
export function createCoverageRateValueCallback(
numeratorFields: string[],
@@ -90,6 +174,24 @@ export function getComputedValueCallback(config: ComputedFieldConfig): DataField
return createSumValueCallback(config.fields);
case 'avg':
return createAvgValueCallback(config.fields);
case 'max':
return createMaxValueCallback(config.fields);
case 'difference':
return createDifferenceValueCallback(config.fields);
case 'differenceOfSums':
return createDifferenceOfSumsValueCallback(
config.minuendFields,
config.subtrahendFields,
);
case 'maxOfFieldAndSum':
return createMaxOfFieldAndSumValueCallback(
config.baseField,
config.sumFields,
);
case 'sumOfMaxOfFieldAndSumGroups':
return createSumOfMaxOfFieldAndSumGroupsValueCallback(
config.groups,
);
case 'pblswrgPopltnSuprtRate':
case 'adtrtPopltnSuprtRate':
return createCoverageRateValueCallback(config.numeratorFields, config.denominatorFields);
@@ -21,6 +21,12 @@ import type {
StatisticsInputSewPipeService,
SeweragePipeInputFactInstallStatusSaveRequest,
SeweragePipeInputFactInstallStatusSaveResponse,
SeweragePipeInputPipeTypeStatusSaveRequest,
SeweragePipeInputPipeTypeStatusSaveResponse,
SeweragePipeInputPipeDiameterStatusSaveRequest,
SeweragePipeInputPipeDiameterStatusSaveResponse,
SeweragePipeInputManholeStatusSaveRequest,
SeweragePipeInputManholeStatusSaveResponse,
} from "../../model/input/statistics-sewage-pipe-input-interface";
export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeService {
@@ -119,6 +125,30 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
);
return response.data;
}
public async savePipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSaveRequest): Promise<SeweragePipeInputPipeTypeStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputPipeTypeStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-type-status/save`,
param,
);
return response.data;
}
public async savePipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSaveRequest): Promise<SeweragePipeInputPipeDiameterStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputPipeDiameterStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-diameter-status/save`,
param,
);
return response.data;
}
public async saveManholeStatus(param: SeweragePipeInputManholeStatusSaveRequest): Promise<SeweragePipeInputManholeStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputManholeStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/manhole-status/save`,
param,
);
return response.data;
}
};
export const statisticsInputSewPipeApiService = new StatisticsInputSewPipeApiService();
@@ -1,17 +1,4 @@
import type { ApiResponse } from "../statistics-interface";
export interface SewagePipeMaintainRepairStatusInfo {
laePicNm: string | null;
laePicTelNo: string | null;
laeKbrdrDeptNm: string | null;
laeKbrdrJbgdNm: string | null;
laeIdfrNm: string | null;
laeIdfrTelNo: string | null;
laeIdfrDeptNm: string | null;
laeIdfrJbgdNm: string | null;
laeSbmsnSttsCd: string | null;
laeSbmsnYmd: string | null;
}
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
export interface SewagePipeMaintainRepairVerificationResultItem {
vrfcCd: string;
@@ -57,7 +44,7 @@ export interface SewagePipeMaintainRepairItem {
}
export interface SewagePipeMaintainRepairData {
statusInfo: SewagePipeMaintainRepairStatusInfo;
statusInfo: SewerageStatusInfo;
commonInfo: SewagePipeMaintainRepairCommonInfo;
repairStatusList: SewagePipeMaintainRepairItem[];
}
@@ -90,7 +77,7 @@ export interface SewagePipeMaintainDredgingItem {
}
export interface SewagePipeMaintainDredgingData {
statusInfo: SewagePipeMaintainRepairStatusInfo;
statusInfo: SewerageStatusInfo;
commonInfo: SewagePipeMaintainRepairCommonInfo;
pipeDredgingStatusList: SewagePipeMaintainDredgingItem[];
}
@@ -1,3 +1,5 @@
import type { SewerageStatusInfo } from "../statistics-interface";
export interface SeweragePipeRequestParams {
ctpvNm?: string;
sggNm?: string;
@@ -6,19 +8,6 @@ export interface SeweragePipeRequestParams {
mode?: string;
}
export interface SeweragePipeInputStatusInfo {
laePicNm: string | null;
laePicTelNo: string | null;
laeKbrdrDeptNm: string | null;
laeKbrdrJbgdNm: string | null;
laeIdfrNm: string | null;
laeIdfrTelNo: string | null;
laeIdfrDeptNm: string | null;
laeIdfrJbgdNm: string | null;
laeSbmsnSttsCd: string | null;
laeSbmsnYmd: string | null;
}
export interface VerifyResultItem {
vrfcCd: string;
rsnExpln: string;
@@ -93,7 +82,7 @@ export interface SeweragePipeInputRegionalFactStatusItem {
}
export interface SeweragePipeInputRegionalResponseData {
statusInfo: SeweragePipeInputStatusInfo;
statusInfo: SewerageStatusInfo;
commonInfo: SeweragePipeInputCommonInfo;
regionalFactStatusList: SeweragePipeInputRegionalFactStatusItem[];
}
@@ -133,7 +122,7 @@ export interface SeweragePipeInputRegionalFactStatusSaveResponse {
status: string;
}
/** 지역별 시설현황 제출 API 요청 (save item 구조 재사용) */
/** ST00020001 지역별 시설현황 제출 API 요청 (`submitList` 행은 저장과 동일) */
export interface SeweragePipeInputRegionalFactStatusSubmitRequest {
statsYr: string;
ctpvNm: string;
@@ -211,7 +200,7 @@ export interface SeweragePipeInputFactInstallStatusItem {
/** 시설별 설치 현황 — data 본문 */
export interface SeweragePipeInputFactInstallResponseData {
statusInfo: SeweragePipeInputStatusInfo;
statusInfo: SewerageStatusInfo;
commonInfo: SeweragePipeInputCommonInfo;
factInstallStatusList: SeweragePipeInputFactInstallStatusItem[];
}
@@ -223,6 +212,14 @@ export interface SeweragePipeInputFactInstallResponse {
message?: string;
}
/** 관종별 현황 - 연도별 통계 항목(신규 스펙) */
export interface SeweragePipeInputPipeTypeYearlyStatItem {
lyngYr: string;
prlgWhol: number;
lyngPrlg: number;
dmltnPrlg: number;
}
/** 관종별 현황 — 목록 항목 */
export interface SeweragePipeInputPipeTypeStatusItem {
admdstCd: string;
@@ -231,105 +228,20 @@ export interface SeweragePipeInputPipeTypeStatusItem {
pipeSeCd: string;
pipeSeNm: string;
pipeByPrstWhol: number;
lyngPrlg: number;
dmltnPrlg: number;
yy1PrlgWhol: number;
yy1LyngPrlg: number;
yy1DmltnPrlg: number;
yy2PrlgWhol: number;
yy2LyngPrlg: number;
yy2DmltnPrlg: number;
yy3PrlgWhol: number;
yy3LyngPrlg: number;
yy3DmltnPrlg: number;
yy4PrlgWhol: number;
yy4LyngPrlg: number;
yy4DmltnPrlg: number;
yy5PrlgWhol: number;
yy5LyngPrlg: number;
yy5DmltnPrlg: number;
yy6PrlgWhol: number;
yy6LyngPrlg: number;
yy6DmltnPrlg: number;
yy7PrlgWhol: number;
yy7LyngPrlg: number;
yy7DmltnPrlg: number;
yy8PrlgWhol: number;
yy8LyngPrlg: number;
yy8DmltnPrlg: number;
yy9PrlgWhol: number;
yy9LyngPrlg: number;
yy9DmltnPrlg: number;
yy10PrlgWhol: number;
yy10LyngPrlg: number;
yy10DmltnPrlg: number;
yy11PrlgWhol: number;
yy11LyngPrlg: number;
yy11DmltnPrlg: number;
yy12PrlgWhol: number;
yy12LyngPrlg: number;
yy12DmltnPrlg: number;
yy13PrlgWhol: number;
yy13LyngPrlg: number;
yy13DmltnPrlg: number;
yy14PrlgWhol: number;
yy14LyngPrlg: number;
yy14DmltnPrlg: number;
yy15PrlgWhol: number;
yy15LyngPrlg: number;
yy15DmltnPrlg: number;
yy16PrlgWhol: number;
yy16LyngPrlg: number;
yy16DmltnPrlg: number;
yy17PrlgWhol: number;
yy17LyngPrlg: number;
yy17DmltnPrlg: number;
yy18PrlgWhol: number;
yy18LyngPrlg: number;
yy18DmltnPrlg: number;
yy19PrlgWhol: number;
yy19LyngPrlg: number;
yy19DmltnPrlg: number;
yy20PrlgWhol: number;
yy20LyngPrlg: number;
yy20DmltnPrlg: number;
yy21PrlgWhol: number;
yy21LyngPrlg: number;
yy21DmltnPrlg: number;
yy22PrlgWhol: number;
yy22LyngPrlg: number;
yy22DmltnPrlg: number;
yy23PrlgWhol: number;
yy23LyngPrlg: number;
yy23DmltnPrlg: number;
yy24PrlgWhol: number;
yy24LyngPrlg: number;
yy24DmltnPrlg: number;
yy25PrlgWhol: number;
yy25LyngPrlg: number;
yy25DmltnPrlg: number;
yy26PrlgWhol: number;
yy26LyngPrlg: number;
yy26DmltnPrlg: number;
yy27PrlgWhol: number;
yy27LyngPrlg: number;
yy27DmltnPrlg: number;
yy28PrlgWhol: number;
yy28LyngPrlg: number;
yy28DmltnPrlg: number;
yy29PrlgWhol: number;
yy29LyngPrlg: number;
yy29DmltnPrlg: number;
yy30PrlgWhol: number;
yy30LyngPrlg: number;
yy30DmltnPrlg: number;
/** 구 스펙 */
lyngPrlg?: number;
dmltnPrlg?: number;
/** 신 스펙 */
lyngPrlgWhol?: number;
dmltnPrlgWhol?: number;
yearlyStatList?: SeweragePipeInputPipeTypeYearlyStatItem[];
rmrk: string;
verificationResult?: SeweragePipeInputVerificationResult;
}
/** 관종별 현황 — data 본문 */
export interface SeweragePipeInputPipeTypeResponseData {
statusInfo: SeweragePipeInputStatusInfo;
statusInfo: SewerageStatusInfo;
commonInfo: SeweragePipeInputCommonInfo;
pipeTypeStatusList: SeweragePipeInputPipeTypeStatusItem[];
}
@@ -341,6 +253,36 @@ export interface SeweragePipeInputPipeTypeResponse {
message?: string;
}
/** 관종별 현황 저장 — saveList 항목 */
export interface SeweragePipeInputPipeTypeStatusSaveItem {
admdstCd: string;
admdstNm: string;
pipeSeCd: string;
yearlyStatList: SeweragePipeInputPipeTypeYearlyStatItem[];
rmrk: string;
lyngPrlgWhol: number;
dmltnPrlgWhol: number;
pipeByPrstWhol: number;
}
/** 관종별 현황 저장 API 요청 */
export interface SeweragePipeInputPipeTypeStatusSaveRequest {
statsYr: string;
ctpvNm: string;
sggNm: string;
sewerBzmnCd: string;
userId: string;
superAdmin: string;
saveList: SeweragePipeInputPipeTypeStatusSaveItem[];
}
/** 관종별 현황 저장 API 응답 */
export interface SeweragePipeInputPipeTypeStatusSaveResponse {
data?: string;
message?: string;
status: string;
}
/** 관경별 현황 — 목록 항목 */
export interface SeweragePipeInputPipeDiameterStatusItem {
admdstCd: string;
@@ -362,7 +304,7 @@ export interface SeweragePipeInputPipeDiameterStatusItem {
/** 관경별 현황 — data 본문 */
export interface SeweragePipeInputPipeDiameterResponseData {
statusInfo: SeweragePipeInputStatusInfo;
statusInfo: SewerageStatusInfo;
commonInfo: SeweragePipeInputCommonInfo;
pipeDiameterStatusList: SeweragePipeInputPipeDiameterStatusItem[];
}
@@ -374,11 +316,69 @@ export interface SeweragePipeInputPipeDiameterResponse {
message?: string;
}
/** 맨홀 현황 — 목록 항목 (yy14=2014년 … yy25=2025년) */
export interface SeweragePipeInputManholeStatusItem {
/** 관경별 현황 저장 — saveList 항목 */
export interface SeweragePipeInputPipeDiameterStatusSaveItem {
pipeSeCd: string;
step1PdmtPrlg: number;
step2PdmtPrlg: number;
step3PdmtPrlg: number;
step4PdmtPrlg: number;
step5PdmtPrlg: number;
sqrePdmtPrlg: number;
etcPdmtPrlg: number;
rmrk: string;
pdmtByTsmWhol: number;
admdstCd: string;
admdstNm: string;
}
/** 관경별 현황 저장 API 요청 */
export interface SeweragePipeInputPipeDiameterStatusSaveRequest {
statsYr: string;
ctpvNm: string;
sggNm: string;
sewerBzmnCd: string;
userId: string;
superAdmin: string;
saveList: SeweragePipeInputPipeDiameterStatusSaveItem[];
}
/** 관경별 현황 저장 API 응답 */
export interface SeweragePipeInputPipeDiameterStatusSaveResponse {
data?: string;
message?: string;
status: string;
}
/** 맨홀 현황 — 연도별 통계 항목 */
export interface SeweragePipeInputManholeYearlyStatItem {
manhYr: string;
wholMnhlCnt: number;
step1MnhlCnt: number;
step2MnhlCnt: number;
step3MnhlCnt: number;
step4MnhlCnt: number;
step5MnhlCnt: number;
}
type SeweragePipeInputManholeLegacyYy = 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
type SeweragePipeInputManholeLegacySuffix =
| "WholMnhlCnt"
| "Step1MnhlCnt"
| "Step2MnhlCnt"
| "Step3MnhlCnt"
| "Step4MnhlCnt"
| "Step5MnhlCnt";
type SeweragePipeInputManholeLegacyYearField =
`yy${SeweragePipeInputManholeLegacyYy}${SeweragePipeInputManholeLegacySuffix}`;
/** 맨홀 현황 — 목록 항목 */
export interface SeweragePipeInputManholeStatusItem
extends Partial<Record<SeweragePipeInputManholeLegacyYearField, number>> {
sggNm: string;
admdstCd: string;
admdstNm: string;
emphsMngSeCd: string;
emphsMngSeNm: string;
totalWholMnhlCnt: number;
totalStep1MnhlCnt: number;
@@ -386,85 +386,14 @@ export interface SeweragePipeInputManholeStatusItem {
totalStep3MnhlCnt: number;
totalStep4MnhlCnt: number;
totalStep5MnhlCnt: number;
yy14WholMnhlCnt: number;
yy14Step1MnhlCnt: number;
yy14Step2MnhlCnt: number;
yy14Step3MnhlCnt: number;
yy14Step4MnhlCnt: number;
yy14Step5MnhlCnt: number;
yy15WholMnhlCnt: number;
yy15Step1MnhlCnt: number;
yy15Step2MnhlCnt: number;
yy15Step3MnhlCnt: number;
yy15Step4MnhlCnt: number;
yy15Step5MnhlCnt: number;
yy16WholMnhlCnt: number;
yy16Step1MnhlCnt: number;
yy16Step2MnhlCnt: number;
yy16Step3MnhlCnt: number;
yy16Step4MnhlCnt: number;
yy16Step5MnhlCnt: number;
yy17WholMnhlCnt: number;
yy17Step1MnhlCnt: number;
yy17Step2MnhlCnt: number;
yy17Step3MnhlCnt: number;
yy17Step4MnhlCnt: number;
yy17Step5MnhlCnt: number;
yy18WholMnhlCnt: number;
yy18Step1MnhlCnt: number;
yy18Step2MnhlCnt: number;
yy18Step3MnhlCnt: number;
yy18Step4MnhlCnt: number;
yy18Step5MnhlCnt: number;
yy19WholMnhlCnt: number;
yy19Step1MnhlCnt: number;
yy19Step2MnhlCnt: number;
yy19Step3MnhlCnt: number;
yy19Step4MnhlCnt: number;
yy19Step5MnhlCnt: number;
yy20WholMnhlCnt: number;
yy20Step1MnhlCnt: number;
yy20Step2MnhlCnt: number;
yy20Step3MnhlCnt: number;
yy20Step4MnhlCnt: number;
yy20Step5MnhlCnt: number;
yy21WholMnhlCnt: number;
yy21Step1MnhlCnt: number;
yy21Step2MnhlCnt: number;
yy21Step3MnhlCnt: number;
yy21Step4MnhlCnt: number;
yy21Step5MnhlCnt: number;
yy22WholMnhlCnt: number;
yy22Step1MnhlCnt: number;
yy22Step2MnhlCnt: number;
yy22Step3MnhlCnt: number;
yy22Step4MnhlCnt: number;
yy22Step5MnhlCnt: number;
yy23WholMnhlCnt: number;
yy23Step1MnhlCnt: number;
yy23Step2MnhlCnt: number;
yy23Step3MnhlCnt: number;
yy23Step4MnhlCnt: number;
yy23Step5MnhlCnt: number;
yy24WholMnhlCnt: number;
yy24Step1MnhlCnt: number;
yy24Step2MnhlCnt: number;
yy24Step3MnhlCnt: number;
yy24Step4MnhlCnt: number;
yy24Step5MnhlCnt: number;
yy25WholMnhlCnt: number;
yy25Step1MnhlCnt: number;
yy25Step2MnhlCnt: number;
yy25Step3MnhlCnt: number;
yy25Step4MnhlCnt: number;
yy25Step5MnhlCnt: number;
yearlyStats?: SeweragePipeInputManholeYearlyStatItem[];
rmrk: string;
verificationResult?: SeweragePipeInputVerificationResult;
}
/** 맨홀 현황 — data 본문 */
export interface SeweragePipeInputManholeResponseData {
statusInfo: SeweragePipeInputStatusInfo;
statusInfo: SewerageStatusInfo;
commonInfo: SeweragePipeInputCommonInfo;
manholeStatusList: SeweragePipeInputManholeStatusItem[];
}
@@ -476,6 +405,44 @@ export interface SeweragePipeInputManholeResponse {
message?: string;
}
/** 맨홀 현황 저장 — yearlyStats 항목 */
export interface SeweragePipeInputManholeStatusSaveYearlyStatItem {
manhYr: string;
wholMnhlCnt: number;
step1MnhlCnt: number;
step2MnhlCnt: number;
step3MnhlCnt: number;
step4MnhlCnt: number;
step5MnhlCnt: number;
}
/** 맨홀 현황 저장 — saveList 항목 */
export interface SeweragePipeInputManholeStatusSaveItem {
emphsMngSeCd: string;
admdstCd: string;
admdstNm: string;
yearlyStats: SeweragePipeInputManholeStatusSaveYearlyStatItem[];
rmrk: string;
}
/** 맨홀 현황 저장 API 요청 */
export interface SeweragePipeInputManholeStatusSaveRequest {
statsYr: string;
ctpvNm: string;
sggNm: string;
sewerBzmnCd: string;
userId: string;
superAdmin: string;
saveList: SeweragePipeInputManholeStatusSaveItem[];
}
/** 맨홀 현황 저장 API 응답 */
export interface SeweragePipeInputManholeStatusSaveResponse {
data?: string;
message?: string;
status: string;
}
/** 시설별 현황 관리 - 하수처리장별 연결 현황 목록 항목 */
export interface SeweragePipeInputFacilityConnectionItem {
fcltNm: string;
@@ -497,7 +464,7 @@ export interface SeweragePipeInputAdminAreaConnectionItem {
/** 시설별 현황 관리 — data 본문 */
export interface SeweragePipeInputConnectionStatusResponseData {
statusInfo: SeweragePipeInputStatusInfo;
statusInfo: SewerageStatusInfo;
commonInfo: SeweragePipeInputCommonInfo;
facilityConnectionList: SeweragePipeInputFacilityConnectionItem[];
adminAreaConnectionList: SeweragePipeInputAdminAreaConnectionItem[];
@@ -604,4 +571,7 @@ export interface StatisticsInputSewPipeService {
saveRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSaveRequest): Promise<SeweragePipeInputRegionalFactStatusSaveResponse>;
submitRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSubmitRequest): Promise<SeweragePipeInputRegionalFactStatusSubmitResponse>;
saveFactInstallStatus(param: SeweragePipeInputFactInstallStatusSaveRequest): Promise<SeweragePipeInputFactInstallStatusSaveResponse>;
savePipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSaveRequest): Promise<SeweragePipeInputPipeTypeStatusSaveResponse>;
savePipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSaveRequest): Promise<SeweragePipeInputPipeDiameterStatusSaveResponse>;
saveManholeStatus(param: SeweragePipeInputManholeStatusSaveRequest): Promise<SeweragePipeInputManholeStatusSaveResponse>;
}
@@ -1,23 +1,6 @@
import type { ApiResponse } from "../statistics-interface";
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
// 하수도 보급률 데이터 항목
export interface SewerageSupplyRateStatusInfo {
laeSbmsnSttsCd: string | null;
laeSbmsnYmd: string | null;
laePicNm: string | null;
laePicTelNo: string | null;
laeKbrdrJbgdNm: string | null;
prvnStatus: string | null;
dcsnStatus: string | null;
wrterOpinionSm: string | null;
chckerOpinionSm: string | null;
laeIdfrDeptNm: string | null;
laeIdfrJbgdNm: string | null;
laeIdfrNm: string | null;
laeIdfrTelNo: string | null;
laeKbrdrDeptNm: string | null;
}
export interface SewerageSupplyRateCommonInfo {
sewerBzmnArea: number;
sewerBzmnCd: string;
@@ -84,7 +67,7 @@ export interface SewerageSupplyRateItem {
}
export interface SewerageSupplyRateResponseData {
statusInfo: SewerageSupplyRateStatusInfo;
statusInfo: SewerageStatusInfo;
sewerageSupplyRateList: SewerageSupplyRateItem[];
commonInfo: SewerageSupplyRateCommonInfo;
}
@@ -35,6 +35,21 @@ export interface ApiResponse<T> {
message?: string;
}
/** 하수 입력/보급률 등 조회 `data.statusInfo` 공통 (LAE 제출·담당자 정보) */
export interface SewerageStatusInfo {
laePicNm: string | null;
laePicTelNo: string | null;
laeKbrdrDeptNm: string | null;
laeKbrdrJbgdNm: string | null;
laeIdfrNm: string | null;
laeIdfrTelNo: string | null;
laeIdfrDeptNm: string | null;
laeIdfrJbgdNm: string | null;
laeSbmsnSttsCd: string | null;
laeSbmsnYmd: string | null;
rjctOpnnCn: string | null;
}
export interface ImrRequest {
user_id?: string;
sewer_bzmn_cd: string;
@@ -268,17 +283,17 @@ export interface ReviewSubmitResponse {
message?: string;
}
export interface SubmitVerifyExceptionItem {
vrfcSn: number;
prcsOpnnExpln: string;
}
export interface SubmitVerifyExceptionRequest {
sewerBzmnCd: string;
statsYr: string;
statsCd: string;
userId: string;
exceptionList: [
{
vrfcSn: number;
prcsOpnnExpln: string;
}
];
exceptionList: SubmitVerifyExceptionItem[];
}
export interface SubmitVerifyExceptionResponse {