diff --git a/apps/st/src/components/module/CompactDataGrid.tsx b/apps/st/src/components/module/CompactDataGrid.tsx index 985e98ea6..a8605c52e 100644 --- a/apps/st/src/components/module/CompactDataGrid.tsx +++ b/apps/st/src/components/module/CompactDataGrid.tsx @@ -3,9 +3,27 @@ import { ValueType } from "realgrid" import RealGrid, { GridFitStyle } from "realgrid" import { RealGridReact, RGDataColumn } from "realgrid-react" -import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback, CellStyleCallback } from "../type/DataGrid.type" +import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback, CellStyleCallback, CellStyleCallbackResult, CellStyleCallbackParams } from "../type/DataGrid.type" import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks" +function mergeCellStyleCallbackResult( + baseStyleName: string | undefined, + extra: CellStyleCallbackResult | undefined, +): CellStyleCallbackResult { + if (!extra) { + return baseStyleName ? { styleName: baseStyleName } : {}; + } + if (typeof extra === "string") { + const styleName = [extra, baseStyleName].filter(Boolean).join(" "); + return styleName ? { styleName } : {}; + } + const styleName = [extra.styleName, baseStyleName].filter(Boolean).join(" "); + return { + ...extra, + ...(styleName ? { styleName } : {}), + }; +} + // RealGrid 타입 확장 declare module "realgrid" { interface GridView { @@ -411,18 +429,33 @@ const CompactDataGrid = ({ columns.forEach((column) => { const baseStyleName = column.styleName; const gridCol = currentGridView.columnByName(column.name) as { - styleCallback?: (g: unknown, dataCell: { itemIndex?: number; index?: { itemIndex?: number }; dataField?: string; fieldName?: string }) => string; + styleCallback?: ( + g: unknown, + dataCell: { + itemIndex?: number; + index?: { itemIndex?: number }; + dataField?: string; + fieldName?: string; + }, + ) => CellStyleCallbackResult; } | null; if (gridCol) { - gridCol.styleCallback = (_g, dataCell) => { + gridCol.styleCallback = (grid, dataCell) => { const itemIndex = dataCell?.index?.itemIndex ?? dataCell?.itemIndex ?? -1; - const fieldName = (dataCell?.dataField ?? dataCell?.fieldName ?? column.fieldName) ?? ''; + const fieldName = + (dataCell?.dataField ?? dataCell?.fieldName ?? column.fieldName) ?? ""; const data = rowDataRef.current; - if (itemIndex < 0 || itemIndex >= data.length) return baseStyleName ?? ''; + if (itemIndex < 0 || itemIndex >= data.length) { + return mergeCellStyleCallbackResult(baseStyleName, undefined); + } const row = data[itemIndex] as Record; - const extra = cellStyleCallbackRef.current?.({ rowIndex: itemIndex, fieldName, row }); - if (!extra) return baseStyleName ?? ''; - return baseStyleName ? `${extra} ${baseStyleName}` : extra; + const extra = cellStyleCallbackRef.current?.({ + rowIndex: itemIndex, + fieldName, + row, + grid: grid as CellStyleCallbackParams["grid"], + }); + return mergeCellStyleCallbackResult(baseStyleName, extra); }; } }); diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index 9cc364c04..957318ab9 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -11,6 +11,8 @@ import { Button } from "@common/components"; import type { CellStyleCallback, + CellStyleCallbackParams, + CellStyleCallbackResult, CellTooltipCallback, ColumnLayoutItem, DataGridProps, @@ -28,6 +30,24 @@ import { } from "@/components/util/gridExcelExport"; import { useTabStore } from "@/store/tab-store"; +function mergeCellStyleCallbackResult( + baseStyleName: string | undefined, + extra: CellStyleCallbackResult | undefined, +): CellStyleCallbackResult { + if (!extra) { + return baseStyleName ? { styleName: baseStyleName } : {}; + } + if (typeof extra === "string") { + const styleName = [extra, baseStyleName].filter(Boolean).join(" "); + return styleName ? { styleName } : {}; + } + const styleName = [extra.styleName, baseStyleName].filter(Boolean).join(" "); + return { + ...extra, + ...(styleName ? { styleName } : {}), + }; +} + // RealGrid 타입 확장 declare module "realgrid" { interface GridView { @@ -570,6 +590,14 @@ function buildFieldsFromColumns( "denominatorQuotient" in col.computed && col.computed.numeratorQuotient.length === 2 && col.computed.denominatorQuotient.length === 2; + const hasQuotientTimesMultiplierPercent = + col.computed.type === "quotientTimesMultiplierPercent" && + "numeratorField" in col.computed && + "denominatorField" in col.computed && + "multiplierField" in col.computed && + Boolean(col.computed.numeratorField) && + Boolean(col.computed.denominatorField) && + Boolean(col.computed.multiplierField); if ( hasFields || hasNumeratorDenominator || @@ -581,7 +609,8 @@ function buildFieldsFromColumns( hasPumpGrossPpqty || hasBodPrcsRt || hasSumPerTenThousand || - hasQuotientRatioPercent + hasQuotientRatioPercent || + hasQuotientTimesMultiplierPercent ) { const computedCallback = getComputedValueCallback( col.computed, @@ -1922,7 +1951,7 @@ const DataGrid = ({ dataField?: string; fieldName?: string; }, - ) => string; + ) => CellStyleCallbackResult; renderer?: { showTooltip?: boolean }; } | null; if (gridCol) { @@ -1961,7 +1990,7 @@ const DataGrid = ({ { showTooltip: true }, ); } - gridCol.styleCallback = (_g, dataCell) => { + gridCol.styleCallback = (grid, dataCell) => { const itemIndex = dataCell?.index?.itemIndex ?? dataCell?.itemIndex ?? @@ -1974,8 +2003,12 @@ const DataGrid = ({ if ( itemIndex < 0 || itemIndex >= data.length - ) - return baseStyleName ?? ""; + ) { + return mergeCellStyleCallbackResult( + baseStyleName, + undefined, + ); + } const row = data[itemIndex] as Record< string, string | number @@ -1985,11 +2018,12 @@ const DataGrid = ({ rowIndex: itemIndex, fieldName, row, + grid: grid as CellStyleCallbackParams["grid"], }); - if (!extra) return baseStyleName ?? ""; - return baseStyleName - ? `${extra} ${baseStyleName}` - : extra; + return mergeCellStyleCallbackResult( + baseStyleName, + extra, + ); }; } }); diff --git a/apps/st/src/components/module/TabDataGrid.tsx b/apps/st/src/components/module/TabDataGrid.tsx index a1fb327a8..6b7eacc87 100644 --- a/apps/st/src/components/module/TabDataGrid.tsx +++ b/apps/st/src/components/module/TabDataGrid.tsx @@ -10,9 +10,27 @@ import { useAlertStore } from "@/shared/lib"; import { Tabs, TabsContent,TabsList, TabsTrigger } from "@common/components"; import { Button } from "@common/components"; -import type { CellStyleCallback,ColumnConfig, ColumnInfo, ColumnLayoutItem, FooterItemWithCallback, GridViewWithSetColumns, RealGridColumnInfo, TabDataGridProps } from "../type/DataGrid.type" +import type { CellStyleCallback, CellStyleCallbackParams, CellStyleCallbackResult, ColumnConfig, ColumnInfo, ColumnLayoutItem, FooterItemWithCallback, GridViewWithSetColumns, RealGridColumnInfo, TabDataGridProps } from "../type/DataGrid.type" import GridTopActionButton from "./GridTopActionButton"; + +function mergeCellStyleCallbackResult( + baseStyleName: string | undefined, + extra: CellStyleCallbackResult | undefined, +): CellStyleCallbackResult { + if (!extra) { + return baseStyleName ? { styleName: baseStyleName } : {}; + } + if (typeof extra === "string") { + const styleName = [extra, baseStyleName].filter(Boolean).join(" "); + return styleName ? { styleName } : {}; + } + const styleName = [extra.styleName, baseStyleName].filter(Boolean).join(" "); + return { + ...extra, + ...(styleName ? { styleName } : {}), + }; +} import SplitDataGrid from "./SplitDataGrid"; import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks" @@ -73,10 +91,12 @@ function safeGridViewRefresh(gv: { refresh?: () => void } | null | undefined) { function safeColumnByName( gv: { columnByName?: (name: string) => unknown } | null | undefined, name: string, -): { styleCallback?: (g: unknown, dataCell: Record) => string } | null { +): { styleCallback?: (g: unknown, dataCell: Record) => CellStyleCallbackResult } | null { if (!gv?.columnByName) return null; try { - const col = gv.columnByName(name) as { styleCallback?: (g: unknown, dataCell: Record) => string } | null; + const col = gv.columnByName(name) as { + styleCallback?: (g: unknown, dataCell: Record) => CellStyleCallbackResult; + } | null; return col ?? null; } catch { return null; @@ -296,7 +316,7 @@ const TabDataGrid = ({ const gridCol = safeColumnByName(gv, column.name); if (!gridCol) continue; const baseStyleName = column.styleName; - gridCol.styleCallback = (_g, dataCell) => { + gridCol.styleCallback = (grid, dataCell) => { const dc = dataCell as { itemIndex?: number; index?: { itemIndex?: number }; @@ -307,16 +327,16 @@ const TabDataGrid = ({ const fieldName = dc?.dataField ?? dc?.fieldName ?? column.fieldName; const data = rowDataMapRef.current.get(tabKey) || []; if (itemIndex < 0 || itemIndex >= data.length) { - return baseStyleName ?? ""; + return mergeCellStyleCallbackResult(baseStyleName, undefined); } const row = data[itemIndex] as Record; const extra = cellStyleCallbackRef.current?.({ rowIndex: itemIndex, fieldName, row, + grid: grid as CellStyleCallbackParams["grid"], }); - if (!extra) return baseStyleName ?? ""; - return baseStyleName ? `${extra} ${baseStyleName}` : extra; + return mergeCellStyleCallbackResult(baseStyleName, extra); }; attachedCount += 1; } diff --git a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx index de789139c..946e17aa3 100644 --- a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx @@ -1140,8 +1140,8 @@ const PrivateSewageFacilityInp = () => { onValuesChange={setSearchValues} /> {showActionToolbarCard && ( -
-
+
+
@@ -1150,7 +1150,7 @@ const PrivateSewageFacilityInp = () => { void lastYearDataFunc()} /> - + )} {activeTabStatus?.laeSbmsnSttsCd === "2" && ( diff --git a/apps/st/src/components/statistics/input/PumpingStationInp.tsx b/apps/st/src/components/statistics/input/PumpingStationInp.tsx index 0a4be8402..421405505 100644 --- a/apps/st/src/components/statistics/input/PumpingStationInp.tsx +++ b/apps/st/src/components/statistics/input/PumpingStationInp.tsx @@ -967,7 +967,7 @@ const PumpingStationInp = () => { onValuesChange={setSearchValues} /> {showActionToolbarCard && ( -
+
{ void getLastYearDataFunc()} /> - + )} {statusInfo?.laeSbmsnSttsCd === "2" && ( )} {showSaveInToolbar && } -
-
+
+
)} {showArticleToolbarCard && ( -
+
{showNotApplicable && ( { setIsCheck={setIsNotApplicable} /> )} - - -
-
+ + +
+
)} {isLoading && (
@@ -1008,14 +1008,14 @@ const PumpingStationInp = () => {
)} - } columnInfo={columnInfo} rowStartNum={PumpingStationInpColumn.rowStartNum} fixedColumns={PumpingStationInpColumn.fixedColumns} - rowData={rowData} - setRowData={setRowData} + rowData={rowData} + setRowData={setRowData} getGridDataRef={gridGetRowsRef} showHeaderCheckbox statusInfo={statusInfo} diff --git a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx index b4470e438..f9f722b4f 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx @@ -1,81 +1,2062 @@ -import { useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction } from "react"; + +import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; +import { ValueType } from "realgrid"; +import type { RealGridReact } from "realgrid-react"; +import { flushSync } from "react-dom"; + +import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import SearchForm from "@/components/module/SearchForm"; -import TabDataGrid from "@/components/module/TabDataGrid"; -import SubmitButton from "@/components/button/SumitButton"; -import SaveButton from "@/components/button/SaveButton"; -import SubmitExceptionButton from "@/components/button/SubmitExceptionButton"; -import LastYearDataButton from "@/components/button/LastYearDataButton"; import AddArticleButton from "@/components/button/AddArticleButton"; +import CancelSubmitButton from "@/components/button/CancelSubmitButton"; import DeleteArticleButton from "@/components/button/DeleteArticleButton"; - -import { PublicInstitutionSewerIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn" -import { PublicInstitutionExcretaIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn" +import LastYearDataButton from "@/components/button/LastYearDataButton"; +import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton"; +import SaveButton from "@/components/button/SaveButton"; +import SubmitButton from "@/components/button/SumitButton"; +import VerificationResultButton from "@/components/button/VerificationResultButton"; +import DataGrid from "@/components/module/DataGrid"; +import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results"; +import SearchForm from "@/components/module/SearchForm"; +import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; +import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; +import type { + CellStyleCallback, + CellStyleCallbackParams, + CellStyleCallbackResult, + CellTooltipCallbackParams, + ColumnConfig, + ColumnInfo, +} from "@/components/type/DataGrid.type"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; +import { doList } from "@/components/util/column/input/DoList"; import { ExcretaRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/ExcretaRelatedIndustryInpColumn"; import { PrivateSewageTreatRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn"; +import { PublicInstitutionExcretaIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn"; +import { PublicInstitutionSewerIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { statisticsInputSewageExcretaRelatedApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-related-service"; +import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; +import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import type { + ExcretaRelatedIndustrySaveItem, + PrivateSewageTreatRelatedIndustrySaveItem, + PublicInstitutionSewerIndustrySaveItem, + SewageExcretaRelatedRequestParams, + SewageExcretaRelatedVerificationResult, +} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface"; +import { + SEWAGE_EXCRETA_EXCRETA_RELATED_INDUSTRY_STATS_CD, + SEWAGE_EXCRETA_PRIVATE_SEWAGE_TREAT_RELATED_INDUSTRY_STATS_CD, + SEWAGE_EXCRETA_PUBLIC_INSTITUTION_EXCRETA_STATS_CD, + SEWAGE_EXCRETA_PUBLIC_INSTITUTION_SEWER_STATS_CD, +} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface"; +import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface"; +import type { + ApiResponse, + cancelSubmitRequest, + ImrStatusResponse, + SewerageStatusInfo, + Sgg, +} from "@/entities/statistics/model/statistics-interface"; +import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; +import { useAdminStore } from "@/store/admin-store"; +import { getUserInfo } from "../../../../../../packages/common/components/src/api"; + +import SubmitDailog from "./SubmitDailog"; + +const SewageExcretaRelatedInpTabList = [ + PublicInstitutionSewerIndustryInpColumn, + PublicInstitutionExcretaIndustryInpColumn, + ExcretaRelatedIndustryInpColumn, + PrivateSewageTreatRelatedIndustryInpColumn, +] as const; + +const firstTabKey = PublicInstitutionSewerIndustryInpColumn.tabKey; + +/** 직제(ognzSn) 행 추가·제외 — ST00120001·ST00120002 */ +const ARTICLE_EDIT_TAB_KEYS = new Set([ + PublicInstitutionSewerIndustryInpColumn.tabKey, + PublicInstitutionExcretaIndustryInpColumn.tabKey, +]); + +/** 해당사항 없음 — ST00120001·ST00120002 */ +const NOT_APPLICABLE_TAB_KEYS = new Set([ + PublicInstitutionSewerIndustryInpColumn.tabKey, + PublicInstitutionExcretaIndustryInpColumn.tabKey, +]); + +/** 항목 추가 행 — 시군구(sggNm) 드롭다운 선택 */ +const SGG_NM_DROPDOWN_TAB_KEYS = new Set([ + PublicInstitutionSewerIndustryInpColumn.tabKey, + PublicInstitutionExcretaIndustryInpColumn.tabKey, +]); + +/** 저장·제출 API 연동 — ST00120001~ST00120004 */ +const SAVE_SUPPORTED_TAB_KEYS = new Set([ + PublicInstitutionSewerIndustryInpColumn.tabKey, + PublicInstitutionExcretaIndustryInpColumn.tabKey, + ExcretaRelatedIndustryInpColumn.tabKey, + PrivateSewageTreatRelatedIndustryInpColumn.tabKey, +]); + +const SUBMIT_SUPPORTED_TAB_KEYS = SAVE_SUPPORTED_TAB_KEYS; + +/** 행정구역 고정 행 탭 — saveList/submitList만 (ST00120003·ST00120004) */ +const DISTRICT_ROW_TAB_KEYS = new Set([ + ExcretaRelatedIndustryInpColumn.tabKey, + PrivateSewageTreatRelatedIndustryInpColumn.tabKey, +]); + +const ARTICLE_TAB_BY_KEY = new Map( + SewageExcretaRelatedInpTabList.map((tab) => [tab.tabKey, tab]), +); + +type TabToolbarSnapshot = { + showSaveInToolbar: boolean; + showEnterableToolbarButtons: boolean; + showActionToolbarCard: boolean; +}; + +const toGridColumnInfo = (tab: (typeof SewageExcretaRelatedInpTabList)[number]): ColumnInfo => ({ + columns: tab.columns ?? [], + columnLayout: tab.columnLayout ?? [], +}); + +function tabRowDataExists( + rowDataMap: Map[]>, + tabKey: string, +): boolean { + const rows = rowDataMap.get(tabKey); + return Array.isArray(rows) && rows.length > 0; +} + +const computeTabEnterable = ( + statusInfo: SewerageStatusInfo | null | undefined, + periodEnterable: boolean, +): boolean => { + if (statusInfo?.laeSbmsnSttsCd) { + if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { + return false; + } + return periodEnterable; + } + return periodEnterable; +}; + +function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { + return doList.includes(values?.bsnmCd || "") + ? Boolean(values?.gugun && values.gugun !== "ALL") + : true; +} + +function computeTabToolbarSnapshot( + statusInfo: SewerageStatusInfo | null, + hasRowData: boolean, + treatAsSearched: boolean, + searchContext: SearchFormValues | null, + periodEnterable: boolean, + isAdminInfo: boolean, +): TabToolbarSnapshot { + const gugunOk = buildGugunOkForToolbar(searchContext); + const enterable = computeTabEnterable(statusInfo, periodEnterable); + const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; + const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; + const showEnterableToolbarButtons = enterable && gugunOk; + const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk; + const showActionToolbarCard = + (hasRowData || treatAsSearched) && + (showSaveInToolbar || + showEnterableToolbarButtons || + showSubmittedInInputPeriodToolbar || + (isSubmitted && isAdminInfo)); + return { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard }; +} + +function createEmptyArticleRow( + tabKey: string, + ctpvNm: string, + sggNm = "", +): Record { + const row: Record = { ctpvNm, sggNm, __checked: 0 }; + const columns = (ARTICLE_TAB_BY_KEY.get(tabKey)?.columns ?? []) as ColumnConfig[]; + for (const col of columns) { + const fieldName = col.fieldName; + if ( + !fieldName || + fieldName === "ctpvNm" || + fieldName === "sggNm" || + col.editable === false + ) { + continue; + } + if (row[fieldName] === undefined) { + row[fieldName] = col.valueType === ValueType.NUMBER ? 0 : ""; + } + } + return row; +} + +function isCheckedRow(row: Record): boolean { + const v = row.__checked; + return v === 1 || v === "1" || v === "Y" || v === "y" || v === "true"; +} + +type ArticleOgnzSnSet = Set; + +function extractOgnzSnSet(rows: Record[]): ArticleOgnzSnSet { + const set = new Set(); + for (const row of rows) { + const ognzSn = String(row.ognzSn ?? "").trim(); + if (ognzSn) set.add(ognzSn); + } + return set; +} + +function isNewArticleRow(row: Record): boolean { + const v = row.__isNewArticle; + return v === 1 || v === "1"; +} + +function setArticleOgnzSnSetForTab( + storeRef: { current: Map }, + tabKey: string, + rows: Record[], +) { + storeRef.current.set(tabKey, extractOgnzSnSet(rows)); +} + +function clearArticleOgnzSnTracking( + persistedRef: { current: Map }, + preYearRef: { current: Map }, +) { + persistedRef.current = new Map(); + preYearRef.current = new Map(); +} + +/** ST00120001·ST00120002 — deleteList 대상 판별 (`ognzSn` + 전년도·신규 행 구분) */ +function isPersistedPublicInstitutionArticleRow( + tabKey: string, + row: Record, + persistedRef: { current: Map }, + preYearRef: { current: Map }, +): boolean { + if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) return false; + if (isNewArticleRow(row)) return false; + const ognzSn = String(row.ognzSn ?? "").trim(); + if (!ognzSn) return false; + if ((preYearRef.current.get(tabKey) ?? new Set()).has(ognzSn)) return false; + return (persistedRef.current.get(tabKey) ?? new Set()).has(ognzSn); +} + +function syncPublicInstitutionArticleTracking( + tabKey: string, + gridRows: Record[], + fromPreYear: boolean, + persistedRef: { current: Map }, + preYearRef: { current: Map }, + clearDeleteList: (tabKey: string) => void, +) { + if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) return; + if (fromPreYear) { + setArticleOgnzSnSetForTab(preYearRef, tabKey, gridRows); + return; + } + clearDeleteList(tabKey); + setArticleOgnzSnSetForTab(persistedRef, tabKey, gridRows); +} + +function parseSggListFromResponse(res: ApiResponse): Sgg[] { + if (res.status !== "success" || res.data == null) return []; + return Array.isArray(res.data) ? res.data : []; +} + +function buildSggNmColumn( + col: ColumnConfig, + canEdit: boolean, + sggOptions: Sgg[], +): ColumnConfig { + if (!canEdit) { + return { ...col, editable: false as const, styleName: "rg-cell-readonly" }; + } + if (sggOptions.length === 0) { + return { ...col, editable: true, styleName: undefined }; + } + const labels = sggOptions.map((s) => s.gugun); + return { + ...col, + editable: true, + styleName: undefined, + editor: { + type: "dropdown", + domainOnly: true, + textReadOnly: true, + commitOnSelect: true, + dropDownWhenClick: true, + values: labels, + labels, + }, + values: labels, + labels, + lookupDisplay: true, + }; +} + +/** 시군구명(`sggNm`) 기준 `admdstCd`·`admdstNm` — 저장 시 그리드 행에서 해석 */ +function resolvePublicInstitutionAdmdst( + row: Record, + sggOptions: Sgg[], +): { admdstCd: string; admdstNm: string } { + const sggNm = String(row.sggNm ?? "").trim(); + if (sggNm) { + const matched = sggOptions.find((s) => s.gugun === sggNm); + if (matched) { + return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun }; + } + } + const existingCd = String(row.admdstCd ?? "").trim(); + const existingNm = String(row.admdstNm ?? "").trim(); + return { admdstCd: existingCd, admdstNm: existingNm || sggNm }; +} + +function mergeGridRowsWithMapRows( + gridRows: Record[], + mapRows: Record[], + hiddenFields: string[] = [], +): Record[] { + return gridRows.map((gridRow, index) => { + const mapRow = mapRows[index] ?? {}; + const merged: Record = { ...mapRow, ...gridRow }; + if (hiddenFields.length === 0) return merged; + const next = { ...merged }; + for (const field of hiddenFields) { + const fromGrid = String(gridRow[field] ?? "").trim(); + const fromMap = String(mapRow[field] ?? "").trim(); + next[field] = fromGrid || fromMap; + } + return next; + }); +} + +/** 그리드 조회 시 `__isNewArticle` 등 메타 필드 유실 방지 */ +function mergeGridRowsWithArticleMeta( + gridRows: Record[], + mapRows: Record[], +): Record[] { + return gridRows.map((gridRow, index) => { + const mapRow = mapRows[index]; + if (!mapRow) return gridRow; + if (!isNewArticleRow(gridRow) && isNewArticleRow(mapRow)) { + return { + ...gridRow, + __isNewArticle: mapRow.__isNewArticle as string | number, + }; + } + return gridRow; + }); +} + +function nullableTrimmedString( + value: string | number | null | undefined, +): string | null { + const s = String(value ?? "").trim(); + return s === "" ? null : s; +} + +function toGridNumber(value: string | number | null | undefined): number { + const n = Number(value); + return Number.isFinite(n) ? n : 0; +} + +/** ST00120001·ST00120002 — 그리드 행 → 저장/삭제 API 항목 */ +function buildPublicInstitutionIndustrySaveItem( + row: Record, + sggOptions: Sgg[], +): PublicInstitutionSewerIndustrySaveItem { + const { admdstCd, admdstNm } = resolvePublicInstitutionAdmdst(row, sggOptions); + return { + admdstCd, + admdstNm, + ognzSn: String(row.ognzSn ?? "").trim(), + ognzNm: String(row.ognzNm ?? "").trim(), + pbadmsNope: toGridNumber(row.pbadmsNope), + engrkCnt: toGridNumber(row.engrkCnt), + mchnCnt: toGridNumber(row.mchnCnt), + prvtmCnt: toGridNumber(row.prvtmCnt), + chmengCnt: toGridNumber(row.chmengCnt), + envCnt: toGridNumber(row.envCnt), + enjbEtcCnt: toGridNumber(row.enjbEtcCnt), + etcCnt: toGridNumber(row.etcCnt), + rmrk: nullableTrimmedString(row.rmrk), + }; +} + +/** ST00120003 — 그리드 행 → 저장/제출 API 항목 */ +function buildExcretaRelatedIndustrySaveItem( + row: Record, +): ExcretaRelatedIndustrySaveItem { + return { + admdstCd: String(row.admdstCd ?? "").trim(), + admdstNm: String(row.admdstNm ?? "").trim(), + bzentyCnt: toGridNumber(row.bzentyCnt), + empNope: toGridNumber(row.empNope), + n3TonUdrCnt: toGridNumber(row.n3TonUdrCnt), + n4TonUdrCnt: toGridNumber(row.n4TonUdrCnt), + n8TonUdrCnt: toGridNumber(row.n8TonUdrCnt), + etcCnt: toGridNumber(row.etcCnt), + clctCnvncPrfmncQnt: toGridNumber(row.clctCnvncPrfmncQnt), + rmrk: nullableTrimmedString(row.rmrk), + }; +} + +/** ST00120004 — 그리드 행 → 저장/제출 API 항목 */ +function buildPrivateSewageTreatRelatedIndustrySaveItem( + row: Record, +): PrivateSewageTreatRelatedIndustrySaveItem { + return { + admdstCd: String(row.admdstCd ?? "").trim(), + admdstNm: String(row.admdstNm ?? "").trim(), + designMdsrCnt: toGridNumber(row.designMdsrCnt), + designMdsrNope: toGridNumber(row.designMdsrNope), + mfbizCnt: toGridNumber(row.mfbizCnt), + mfbizMdsrNope: toGridNumber(row.mfbizMdsrNope), + mngCnt: toGridNumber(row.mngCnt), + mngMdsrNope: toGridNumber(row.mngMdsrNope), + rmrk: nullableTrimmedString(row.rmrk), + }; +} + +function buildDistrictRowSaveItem( + tabKey: string, + row: Record, +): ExcretaRelatedIndustrySaveItem | PrivateSewageTreatRelatedIndustrySaveItem { + if (tabKey === ExcretaRelatedIndustryInpColumn.tabKey) { + return buildExcretaRelatedIndustrySaveItem(row); + } + return buildPrivateSewageTreatRelatedIndustrySaveItem(row); +} + +function resolveStatsCdForTab( + tabKey: string, + tabStatsCdMap: Map, +): string { + const fromMap = tabStatsCdMap.get(tabKey); + if (fromMap) return fromMap; + if (tabKey === PublicInstitutionSewerIndustryInpColumn.tabKey) { + return SEWAGE_EXCRETA_PUBLIC_INSTITUTION_SEWER_STATS_CD; + } + if (tabKey === PublicInstitutionExcretaIndustryInpColumn.tabKey) { + return SEWAGE_EXCRETA_PUBLIC_INSTITUTION_EXCRETA_STATS_CD; + } + if (tabKey === ExcretaRelatedIndustryInpColumn.tabKey) { + return SEWAGE_EXCRETA_EXCRETA_RELATED_INDUSTRY_STATS_CD; + } + if (tabKey === PrivateSewageTreatRelatedIndustryInpColumn.tabKey) { + return SEWAGE_EXCRETA_PRIVATE_SEWAGE_TREAT_RELATED_INDUSTRY_STATS_CD; + } + return tabKey; +} + +type GridRowWithVerification = Record & { + verificationResult?: SewageExcretaRelatedVerificationResult; +}; + +function mergeCellStyleResults( + ...results: (CellStyleCallbackResult | undefined)[] +): CellStyleCallbackResult | undefined { + const styleNames: string[] = []; + let editable: boolean | undefined; + let readOnly: boolean | undefined; + + for (const result of results) { + if (!result) continue; + if (typeof result === "string") { + styleNames.push(result); + continue; + } + if (result.styleName) styleNames.push(result.styleName); + if (result.editable === false) editable = false; + if (result.readOnly === true) readOnly = true; + } + + const styleName = styleNames.join(" ").trim(); + if (!styleName && editable === undefined && readOnly === undefined) { + return undefined; + } + return { + ...(styleName ? { styleName } : {}), + ...(editable !== undefined ? { editable } : {}), + ...(readOnly !== undefined ? { readOnly } : {}), + }; +} + +function verificationCellStyle({ + fieldName, + row, +}: CellStyleCallbackParams): CellStyleCallbackResult | undefined { + const verification = (row as GridRowWithVerification).verificationResult; + if (!verification) return; + const errors = verification[fieldName]; + if (Array.isArray(errors) && errors.length > 0) { + const hasImr = errors.some((e) => e.vrfcCd === "IMR" || e.vrfcCd === "HB"); + return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error"; + } +} + +function verificationCellTooltip({ + fieldName, + row, +}: CellTooltipCallbackParams): string | undefined { + const verification = (row as GridRowWithVerification).verificationResult; + if (!verification) return; + const errors = verification[fieldName]; + if (Array.isArray(errors) && errors.length > 0) { + return errors + .map((e) => { + const prefix = + e.vrfcCd === "IMR" + ? "IMR검증 - " + : e.vrfcCd === "NV" + ? "일반검증 - " + : "HB검증 - "; + return `${prefix}${e.rsnExpln} +(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; + }) + .filter(Boolean) + .join("\n"); + } +} + +function buildSewageExcretaRelatedSearchParam( + values: SearchFormValues, + overrides?: { mode?: string }, +): SewageExcretaRelatedRequestParams { + return { + statsYr: values.year, + sewerBzmnCd: values.bsnmCd, + ctpvNm: values.sido, + sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...(overrides?.mode ? { mode: overrides.mode } : {}), + }; +} + +function mapListToGridRows( + list: T[] | undefined, + ctpvNm: string, +): GridRowWithVerification[] { + return (list ?? []).map((item) => { + const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification; + if (item.verificationResult) { + row.verificationResult = item.verificationResult; + } + return row; + }); +} + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +async function waitForImrJobCompleted( + jobId: string, + getStatus: (id: string) => Promise, + options?: { + pollIntervalMs?: number; + maxAttempts?: number; + onProgress?: (jobProgress: number) => void; + }, +): Promise { + 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 검증 완료 대기 시간이 초과되었습니다."); +} // 하수도 통계 관리 > 12. 하수 및 분뇨관련산업 const SewageExcretaRelatedInp = () => { - - // tab list - const SewageExcretaRelatedInpTabList = [ - PublicInstitutionSewerIndustryInpColumn, - PublicInstitutionExcretaIndustryInpColumn, - ExcretaRelatedIndustryInpColumn, - PrivateSewageTreatRelatedIndustryInpColumn - ]; + const { setMessage: alert } = useAlertStore((state) => state); + const { confirm } = useConfirm(); + const isAdminInfo = useAdminStore((state) => state.isAdminInfo); + const toolbarGroupAllowed = useStatisticsInputToolbarGroupGate(); + 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 submitExceptionFunc = () => { - // 예외 제출 로직 + const [activeTab, setActiveTab] = useState(firstTabKey); + const [rowDataMap, setRowDataMap] = useState< + Map[]> + >(new Map()); + const [tabStatusInfoMap, setTabStatusInfoMap] = useState< + Map + >(new Map()); + const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< + Map + >(new Map()); + const [columnGuidesByTab, setColumnGuidesByTab] = useState< + Record> + >({}); + const [searchValues, setSearchValues] = useState(null); + const [lastSearchedValues, setLastSearchedValues] = useState( + null, + ); + const [hasSearched, setHasSearched] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); + const [loadedTabKeys, setLoadedTabKeys] = useState>(new Set()); + const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isNotApplicableByTab, setIsNotApplicableByTab] = useState>( + () => new Map(), + ); + const [deleteListByTab, setDeleteListByTab] = useState< + Map[]> + >(() => new Map()); + const [sggOptions, setSggOptions] = useState([]); + const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); + const [showSubmitDialog, setShowSubmitDialog] = useState(false); + const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false); + + const searchDoneRef = useRef(false); + const periodEnterableRef = useRef(true); + const searchParamRef = useRef(null); + const activeTabRef = useRef(activeTab); + const loadedTabKeysRef = useRef>(new Set()); + const getGridDataRef = useRef<(() => Record[]) | null>(null); + const getGridRef = useRef(null); + /** ST00120001·ST00120002 — 당해 조회 API로 로드된 ognzSn (deleteList 대상) */ + const persistedArticleOgnzSnByTabRef = useRef>(new Map()); + /** ST00120001·ST00120002 — 전년도(PRE) 조회로 로드된 ognzSn */ + const preYearArticleOgnzSnByTabRef = useRef>(new Map()); + const sggOptionsSidoRef = useRef(null); + activeTabRef.current = activeTab; + + useEffect(() => { + let cancelled = false; + void (async () => { + const entries = await Promise.all( + SewageExcretaRelatedInpTabList.map(async (tab) => { + const tabKey = tab.tabKey; + try { + const res = await statisticsApi.getColumnGuides(tabKey); + if (res.status !== "success" || !Array.isArray(res.data)) { + return [tabKey, {}] as const; + } + const next: Record = {}; + for (const item of res.data) { + const key = item.engColNm?.trim(); + const guide = item.columnGuide?.trim(); + if (key && guide) next[key] = guide; + } + return [tabKey, next] as const; + } catch { + return [tabKey, {}] as const; + } + }), + ); + if (cancelled) return; + setColumnGuidesByTab(Object.fromEntries(entries)); + })(); + return () => { + cancelled = true; + }; + }, []); + + const shouldShowNotApplicable = ( + tabKey: string, + hasRows: boolean, + statusInfo: SewerageStatusInfo | null | undefined, + ): boolean => + NOT_APPLICABLE_TAB_KEYS.has(tabKey) && + hasSearched && + !hasRows && + computeTabEnterable(statusInfo, inputPeriodEnterable); + + const setIsNotApplicableForTab = useCallback((tabKey: string, nextValue: boolean) => { + setIsNotApplicableByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, nextValue); + return next; + }); + }, []); + + const clearDeleteListForTab = useCallback((tabKey: string) => { + setDeleteListByTab((prev) => { + const next = new Map(prev); + next.delete(tabKey); + return next; + }); + }, []); + + const loadSggOptions = useCallback(async (sido: string): Promise => { + if (!sido || sido === "ALL") { + setSggOptions([]); + return []; + } + try { + const res = await statisticsApi.getSggAllList(sido); + const list = parseSggListFromResponse(res); + setSggOptions(list); + return list; + } catch { + setSggOptions([]); + return []; + } + }, []); + + const commitToolbarSnapshotForLoadedTab = ( + tabKey: string, + statusInfo: SewerageStatusInfo | null, + hasRowData: boolean, + searchContext: SearchFormValues, + ) => { + setToolbarSnapshotByTab((prev) => { + const next = new Map(prev); + next.set( + tabKey, + computeTabToolbarSnapshot( + statusInfo, + hasRowData, + searchDoneRef.current, + searchContext, + periodEnterableRef.current, + isAdminInfo, + ), + ); + return next; + }); }; - const lastYearDataFunc = () => { - // 지난연도 데이터 가져오기 로직 + const markTabLoaded = (tabKey: string) => { + loadedTabKeysRef.current.add(tabKey); + setLoadedTabKeys(new Set(loadedTabKeysRef.current)); + }; + + const getTabGridRows = (tabKey: string): Record[] => { + const mapRows = rowDataMap.get(tabKey) ?? []; + if (tabKey !== activeTabRef.current) return mapRows; + const gridRows = getGridDataRef.current?.() ?? mapRows; + if (gridRows.length === 0) return gridRows; + if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey) || mapRows.length === 0) return gridRows; + return mergeGridRowsWithArticleMeta(gridRows, mapRows); + }; + + const commitActiveGrid = () => { + const gridView = getGridRef.current?.gridView as + | { commit?: (force?: boolean) => void; cancel?: () => void } + | undefined; + try { + gridView?.commit?.(true); + } catch { + gridView?.cancel?.(); + } + }; + + const handleActiveTabChange = async (tabKey: string) => { + const prevTabKey = activeTabRef.current; + commitActiveGrid(); + const latestRowsInPrevTab = getTabGridRows(prevTabKey); + if (latestRowsInPrevTab.length > 0 || rowDataMap.has(prevTabKey)) { + setRowDataMap((prev) => { + const next = new Map(prev); + next.set(prevTabKey, latestRowsInPrevTab); + return next; + }); + } + + flushSync(() => { + activeTabRef.current = tabKey; + setActiveTab(tabKey); + }); + + if (!hasSearched || !lastSearchedValues || !searchParamRef.current) return; + if (loadedTabKeysRef.current.has(tabKey)) return; + + try { + setIsLoading(true); + await loadActiveTabData( + tabKey, + searchParamRef.current, + lastSearchedValues, + true, + false, + ); + } catch { + alert("데이터 조회 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + }; + + const applyRowMap = ( + mergeIntoExistingMap: boolean, + apply: (m: Map[]>) => void, + ) => { + if (mergeIntoExistingMap) { + setRowDataMap((prev) => { + const next = new Map(prev); + apply(next); + return next; + }); + } else { + const next = new Map[]>(); + apply(next); + setRowDataMap(next); + } + }; + + const loadActiveTabData = async ( + tabKey: string, + param: SewageExcretaRelatedRequestParams, + searchContext: SearchFormValues, + mergeIntoExistingMap: boolean, + updateLastSearchedValues: boolean, + overrides?: { mode?: string }, + ): Promise => { + const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param; + const fromPreYear = overrides?.mode === "PRE"; + + setIsNotApplicableForTab(tabKey, false); + + if (tabKey === PublicInstitutionSewerIndustryInpColumn.tabKey) { + const response = + await statisticsInputSewageExcretaRelatedApi.getPublicInstitutionSewerIndustryList( + requestParam, + ); + if (response.status !== "success" || !response.data) { + alert(response.message || "데이터 조회 중 오류가 발생했습니다."); + return false; + } + const { commonInfo, statusInfo, publicInstitutionSewerIndustryList } = response.data; + const gridRows = mapListToGridRows( + publicInstitutionSewerIndustryList, + commonInfo.ctpvNm, + ); + syncPublicInstitutionArticleTracking( + tabKey, + gridRows, + fromPreYear, + persistedArticleOgnzSnByTabRef, + preYearArticleOgnzSnByTabRef, + clearDeleteListForTab, + ); + applyRowMap(mergeIntoExistingMap, (m) => { + m.set(PublicInstitutionSewerIndustryInpColumn.tabKey, gridRows); + }); + setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); + setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + if (updateLastSearchedValues) setLastSearchedValues(searchContext); + commitToolbarSnapshotForLoadedTab( + tabKey, + statusInfo, + gridRows.length > 0, + searchContext, + ); + markTabLoaded(tabKey); + return true; + } + + if (tabKey === PublicInstitutionExcretaIndustryInpColumn.tabKey) { + const response = + await statisticsInputSewageExcretaRelatedApi.getPublicInstitutionExcretaIndustryList( + requestParam, + ); + if (response.status !== "success" || !response.data) { + alert(response.message || "데이터 조회 중 오류가 발생했습니다."); + return false; + } + const { commonInfo, statusInfo, publicInstitutionExcretaIndustryList } = + response.data; + const gridRows = mapListToGridRows( + publicInstitutionExcretaIndustryList, + commonInfo.ctpvNm, + ); + syncPublicInstitutionArticleTracking( + tabKey, + gridRows, + fromPreYear, + persistedArticleOgnzSnByTabRef, + preYearArticleOgnzSnByTabRef, + clearDeleteListForTab, + ); + applyRowMap(mergeIntoExistingMap, (m) => { + m.set(PublicInstitutionExcretaIndustryInpColumn.tabKey, gridRows); + }); + setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); + setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + if (updateLastSearchedValues) setLastSearchedValues(searchContext); + commitToolbarSnapshotForLoadedTab( + tabKey, + statusInfo, + gridRows.length > 0, + searchContext, + ); + markTabLoaded(tabKey); + return true; + } + + if (tabKey === ExcretaRelatedIndustryInpColumn.tabKey) { + const response = + await statisticsInputSewageExcretaRelatedApi.getExcretaRelatedIndustryList( + requestParam, + ); + if (response.status !== "success" || !response.data) { + alert(response.message || "데이터 조회 중 오류가 발생했습니다."); + return false; + } + const { commonInfo, statusInfo, excretaRelatedIndustryList } = response.data; + const gridRows = mapListToGridRows(excretaRelatedIndustryList, commonInfo.ctpvNm); + applyRowMap(mergeIntoExistingMap, (m) => { + m.set(ExcretaRelatedIndustryInpColumn.tabKey, gridRows); + }); + setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); + setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + if (updateLastSearchedValues) setLastSearchedValues(searchContext); + commitToolbarSnapshotForLoadedTab( + tabKey, + statusInfo, + gridRows.length > 0, + searchContext, + ); + markTabLoaded(tabKey); + return true; + } + + if (tabKey === PrivateSewageTreatRelatedIndustryInpColumn.tabKey) { + const response = + await statisticsInputSewageExcretaRelatedApi.getPrivateSewageTreatRelatedIndustryList( + requestParam, + ); + if (response.status !== "success" || !response.data) { + alert(response.message || "데이터 조회 중 오류가 발생했습니다."); + return false; + } + const { commonInfo, statusInfo, privateSewageTreatRelatedIndustryList } = + response.data; + const gridRows = mapListToGridRows( + privateSewageTreatRelatedIndustryList, + commonInfo.ctpvNm, + ); + applyRowMap(mergeIntoExistingMap, (m) => { + m.set(PrivateSewageTreatRelatedIndustryInpColumn.tabKey, gridRows); + }); + setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); + setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + if (updateLastSearchedValues) setLastSearchedValues(searchContext); + commitToolbarSnapshotForLoadedTab( + tabKey, + statusInfo, + gridRows.length > 0, + searchContext, + ); + markTabLoaded(tabKey); + return true; + } + + alert("해당 탭 조회 API가 없습니다."); + return false; + }; + + const handleReset = () => { + setRowDataMap(new Map()); + setTabStatusInfoMap(new Map()); + setToolbarSnapshotByTab(new Map()); + setActiveTab(firstTabKey); + activeTabRef.current = firstTabKey; + setLastSearchedValues(null); + setHasSearched(false); + setInputPeriodEnterable(true); + searchDoneRef.current = false; + periodEnterableRef.current = true; + searchParamRef.current = null; + loadedTabKeysRef.current.clear(); + setLoadedTabKeys(new Set()); + setTabStatsCdMap(new Map()); + setIsNotApplicableByTab(new Map()); + setDeleteListByTab(new Map()); + clearArticleOgnzSnTracking( + persistedArticleOgnzSnByTabRef, + preYearArticleOgnzSnByTabRef, + ); + setSggOptions([]); + sggOptionsSidoRef.current = null; + setVerificationHistoryOpen(false); + }; + + const handleSearch = async (values: SearchFormValues) => { + if (!values.sido || values.sido === "ALL") { + alert("시도를 선택해주세요."); + return; + } + if (!values.bsnmCd) { + alert("사업자를 선택해주세요."); + return; + } + + try { + const periodResponse = await statisticsInputSewSupplyRateApi.checkInputPeriod( + values.year || "", + ); + if (periodResponse.status !== "success") { + alert(periodResponse.message); + return; + } + const periods = Array.isArray(periodResponse.data) ? periodResponse.data : []; + const periodEnterable = isInputPeriodCurrent(periods); + periodEnterableRef.current = periodEnterable; + setInputPeriodEnterable(periodEnterable); + } catch { + alert("입력 기간 확인 중 오류가 발생했습니다."); + return; + } + + const yearChangedFromLastSearch = + lastSearchedValues != null && + (lastSearchedValues.year ?? "") !== (values.year ?? ""); + + if (yearChangedFromLastSearch) { + setRowDataMap(new Map()); + setTabStatusInfoMap(new Map()); + setTabStatsCdMap(new Map()); + setToolbarSnapshotByTab(new Map()); + setIsNotApplicableByTab(new Map()); + setDeleteListByTab(new Map()); + clearArticleOgnzSnTracking( + persistedArticleOgnzSnByTabRef, + preYearArticleOgnzSnByTabRef, + ); + loadedTabKeysRef.current.clear(); + setLoadedTabKeys(new Set()); + activeTabRef.current = firstTabKey; + setActiveTab(firstTabKey); + } + + try { + setIsLoading(true); + searchDoneRef.current = true; + setHasSearched(true); + setSearchValues(values); + + const searchParam = buildSewageExcretaRelatedSearchParam(values); + searchParamRef.current = searchParam; + + const loadedSgg = await loadSggOptions(values.sido); + sggOptionsSidoRef.current = values.sido; + if (loadedSgg.length === 0) { + alert("시군구 목록을 불러오지 못했습니다."); + return; + } + + const ok = await loadActiveTabData( + activeTabRef.current, + searchParam, + values, + !yearChangedFromLastSearch, + true, + ); + if (!ok) return; + } catch { + alert("데이터 조회 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + }; + + const cnacelSubmitFunc = () => { + void (async () => { + try { + if (!(await confirm("제출된 내용을 취소하시겠습니까?"))) return; + + const ctx = lastSearchedValues ?? searchValues; + if (!ctx) { + alert("먼저 조회를 진행해주세요."); + return; + } + + const userInfo = await getUserInfo(); + const tabKey = activeTabRef.current; + const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey; + + const requestData: cancelSubmitRequest = { + sewerBzmnCd: ctx.bsnmCd ?? "", + ctpvNm: ctx.sido ?? "", + sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), + statsYr: ctx.year ?? "", + mdfrId: userInfo.userUnqId, + statsCd: statsCdForTab, + }; + + const response = await statisticsApi.cancelSubmit(requestData); + + if (response.status === "success") { + if (searchParamRef.current) { + setIsLoading(true); + await loadActiveTabData( + tabKey, + searchParamRef.current, + ctx, + false, + false, + ); + } + alert("제출 취소되었습니다."); + } else { + alert(response.message ?? "제출 취소 중 오류가 발생했습니다."); + } + } catch { + alert("제출 취소 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + })(); + }; + + 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 lastYearDataFunc = async () => { + const values = lastSearchedValues ?? searchValues; + if (!values?.sido || values.sido === "ALL") { + alert("시도를 선택해주세요."); + return; + } + if (!values.bsnmCd) { + alert("사업자를 선택해주세요."); + return; + } + if (!hasSearched || !searchParamRef.current) { + alert("먼저 데이터를 조회해주세요."); + return; + } + + try { + setIsLoading(true); + const param = buildSewageExcretaRelatedSearchParam(values, { mode: "PRE" }); + await loadActiveTabData( + activeTabRef.current, + param, + values, + true, + false, + { mode: "PRE" }, + ); + } catch { + alert("데이터 조회 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } }; const submitFunc = () => { - // 제출 로직 + void (async () => { + const tabKey = activeTabRef.current; + if (!SUBMIT_SUPPORTED_TAB_KEYS.has(tabKey)) { + alert("해당 탭은 제출 기능이 준비되지 않았습니다."); + return; + } + if (!lastSearchedValues || !searchParamRef.current) { + alert("먼저 조회를 진행해주세요."); + return; + } + if (!buildGugunOkForToolbar(lastSearchedValues)) { + alert("시군구를 선택해주세요."); + return; + } + + commitActiveGrid(); + const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const currentData = getTabGridRows(tabKey); + if (NOT_APPLICABLE_TAB_KEYS.has(tabKey)) { + if (!noApplicable && currentData.length === 0) { + alert("해당사항 없음을 체크해주세요."); + return; + } + } else if (currentData.length === 0) { + alert("제출할 데이터가 없습니다."); + return; + } + + if (await confirm("제출 하시겠습니까?")) { + setShowSubmitDialog(true); + } + })(); + }; + + const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => { + const tabKey = activeTabRef.current; + if (!SUBMIT_SUPPORTED_TAB_KEYS.has(tabKey)) { + alert("해당 탭은 제출 기능이 준비되지 않았습니다."); + return; + } + if (!lastSearchedValues || !searchParamRef.current) { + alert("먼저 조회를 진행해주세요."); + return; + } + if (!buildGugunOkForToolbar(lastSearchedValues)) { + alert("시군구를 선택해주세요."); + return; + } + + commitActiveGrid(); + + const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey); + const isPublicInstitutionTab = ARTICLE_EDIT_TAB_KEYS.has(tabKey); + const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const currentData = getTabGridRows(tabKey); + const mapRows = rowDataMap.get(tabKey) ?? []; + + const searchContextBase = { + statsYr: lastSearchedValues.year ?? "", + ctpvNm: lastSearchedValues.sido ?? "", + sggNm: + lastSearchedValues.gugun === "ALL" + ? "" + : (lastSearchedValues.gugun ?? ""), + sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + }; + + let submitList: + | PublicInstitutionSewerIndustrySaveItem[] + | ExcretaRelatedIndustrySaveItem[] + | PrivateSewageTreatRelatedIndustrySaveItem[]; + let deleteList: PublicInstitutionSewerIndustrySaveItem[] = []; + let sggList: Sgg[] = []; + + if (isDistrictRowTab) { + const rowsForSubmit = mergeGridRowsWithMapRows(currentData, mapRows, [ + "admdstCd", + "admdstNm", + ]); + if (rowsForSubmit.length === 0) { + alert("제출할 데이터가 없습니다."); + return; + } + submitList = + tabKey === ExcretaRelatedIndustryInpColumn.tabKey + ? rowsForSubmit.map((row) => buildExcretaRelatedIndustrySaveItem(row)) + : rowsForSubmit.map((row) => + buildPrivateSewageTreatRelatedIndustrySaveItem(row), + ); + } else if (isPublicInstitutionTab) { + const sido = lastSearchedValues.sido ?? ""; + let list = sggOptions; + if (sggOptionsSidoRef.current !== sido || list.length === 0) { + list = await loadSggOptions(sido); + sggOptionsSidoRef.current = sido; + } + if (list.length === 0) { + alert("시군구 목록을 불러오지 못했습니다."); + return; + } + sggList = list; + + const rowsForSubmit = mergeGridRowsWithMapRows(currentData, mapRows, [ + "ognzSn", + "admdstCd", + "admdstNm", + "__isNewArticle", + ]); + + submitList = noApplicable + ? [] + : rowsForSubmit.map((row) => + buildPublicInstitutionIndustrySaveItem(row, list), + ); + deleteList = (deleteListByTab.get(tabKey) ?? []) + .filter((row) => + isPersistedPublicInstitutionArticleRow( + tabKey, + row, + persistedArticleOgnzSnByTabRef, + preYearArticleOgnzSnByTabRef, + ), + ) + .map((row) => buildPublicInstitutionIndustrySaveItem(row, list)); + + if (!noApplicable && submitList.length === 0) { + alert("해당사항 없음을 체크해주세요."); + return; + } + } else { + alert("해당 탭은 제출 기능이 준비되지 않았습니다."); + return; + } + + const activeTabStatus = tabStatusInfoMap.get(tabKey) ?? null; + const statsCdForTab = resolveStatsCdForTab(tabKey, tabStatsCdMap); + + const submitRequestBase = { + ...searchContextBase, + laePicNm: submitInfo.laePicNm, + laePicTelNo: submitInfo.laePicTelNo, + laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, + laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, + laeIdfrNm: submitInfo.laeIdfrNm, + laeIdfrTelNo: submitInfo.laeIdfrTelNo, + laeIdfrDeptNm: submitInfo.laeIdfrDeptNm, + laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm, + ...(isPublicInstitutionTab ? { noApplicable } : {}), + ...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}), + }; + + const verificationQuery = { + sewerBzmnCd: searchContextBase.sewerBzmnCd, + ctpvNm: searchContextBase.ctpvNm, + sggNm: searchContextBase.sggNm, + statsYr: searchContextBase.statsYr, + statsCd: statsCdForTab, + }; + + const reloadAfterSubmit = async () => { + if (isPublicInstitutionTab) { + setDeleteListByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, []); + return next; + }); + preYearArticleOgnzSnByTabRef.current.delete(tabKey); + if (sggList.length > 0) { + setSggOptions(sggList); + } + } + await loadActiveTabData( + tabKey, + searchParamRef.current!, + lastSearchedValues, + false, + false, + ); + }; + + try { + showGlobalProgress({ label: "서버에 요청 중...", progress: 0 }); + const userInfo = await getUserInfo(); + setGlobalProgressLabel("일반 검증 진행 중..."); + + const response = + tabKey === ExcretaRelatedIndustryInpColumn.tabKey + ? await statisticsInputSewageExcretaRelatedApi.submitExcretaRelatedIndustry({ + ...submitRequestBase, + userId: userInfo.userUnqId, + submitList: submitList as ExcretaRelatedIndustrySaveItem[], + }) + : tabKey === PrivateSewageTreatRelatedIndustryInpColumn.tabKey + ? await statisticsInputSewageExcretaRelatedApi.submitPrivateSewageTreatRelatedIndustry( + { + ...submitRequestBase, + userId: userInfo.userUnqId, + submitList: + submitList as PrivateSewageTreatRelatedIndustrySaveItem[], + }, + ) + : tabKey === PublicInstitutionSewerIndustryInpColumn.tabKey + ? await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionSewerIndustry( + { + ...submitRequestBase, + userId: userInfo.userUnqId, + submitList: submitList as PublicInstitutionSewerIndustrySaveItem[], + deleteList, + }, + ) + : await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionExcretaIndustry( + { + ...submitRequestBase, + userId: userInfo.userUnqId, + submitList: submitList as PublicInstitutionSewerIndustrySaveItem[], + deleteList, + }, + ); + + 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 reloadAfterSubmit(); + await openVerificationResultDialogIfNeeded( + verificationQuery, + setVerificationResultDialogOpen, + { notify: alert }, + ); + } catch (e) { + await reloadAfterSubmit(); + await openVerificationResultDialogIfNeeded( + verificationQuery, + setVerificationResultDialogOpen, + { notify: alert, notifySubmitComplete: false }, + ); + const msg = + e instanceof Error && e.message + ? e.message + : "IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요."; + alert(msg); + } + } else { + setGlobalProgress(100); + await reloadAfterSubmit(); + await openVerificationResultDialogIfNeeded( + verificationQuery, + setVerificationResultDialogOpen, + { notify: alert }, + ); + } + } catch { + alert("데이터 제출 중 오류가 발생했습니다."); + } finally { + hideGlobalProgress(); + setShowSubmitDialog(false); + } }; const saveFunc = () => { - // 저장 로직 + void (async () => { + const tabKey = activeTabRef.current; + if (!lastSearchedValues) { + alert("먼저 조회를 진행해주세요."); + return; + } + if (!buildGugunOkForToolbar(lastSearchedValues)) { + alert("시군구를 선택해주세요."); + return; + } + if (!SAVE_SUPPORTED_TAB_KEYS.has(tabKey)) { + alert("해당 탭은 저장 기능이 준비되지 않았습니다."); + return; + } + if (!(await confirm("저장 하시겠습니까?"))) return; + + commitActiveGrid(); + + const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey); + const isPublicInstitutionTab = ARTICLE_EDIT_TAB_KEYS.has(tabKey); + const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const currentData = getTabGridRows(tabKey); + const mapRows = rowDataMap.get(tabKey) ?? []; + + const baseSaveRequest = { + statsYr: lastSearchedValues.year ?? "", + ctpvNm: lastSearchedValues.sido ?? "", + sggNm: + lastSearchedValues.gugun === "ALL" + ? "" + : (lastSearchedValues.gugun ?? ""), + sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...(isPublicInstitutionTab && noApplicable + ? { noApplicable: true as const } + : {}), + }; + + try { + setIsLoading(true); + const userInfo = await getUserInfo(); + const userFields = { + userId: userInfo.userUnqId, + ...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}), + }; + + let response: { status: string; message?: string }; + let sggList: Sgg[] = []; + + if (isDistrictRowTab) { + const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [ + "admdstCd", + "admdstNm", + ]); + const saveList = rowsForSave.map((row) => + buildDistrictRowSaveItem(tabKey, row), + ); + if (saveList.length === 0) { + alert("저장할 데이터가 없습니다."); + return; + } + + response = + tabKey === ExcretaRelatedIndustryInpColumn.tabKey + ? await statisticsInputSewageExcretaRelatedApi.saveExcretaRelatedIndustry( + { + ...baseSaveRequest, + ...userFields, + saveList: saveList as ExcretaRelatedIndustrySaveItem[], + }, + ) + : await statisticsInputSewageExcretaRelatedApi.savePrivateSewageTreatRelatedIndustry( + { + ...baseSaveRequest, + ...userFields, + saveList: + saveList as PrivateSewageTreatRelatedIndustrySaveItem[], + }, + ); + } else if (isPublicInstitutionTab) { + const sido = lastSearchedValues.sido ?? ""; + let list = sggOptions; + if (sggOptionsSidoRef.current !== sido || list.length === 0) { + list = await loadSggOptions(sido); + sggOptionsSidoRef.current = sido; + } + if (list.length === 0) { + alert("시군구 목록을 불러오지 못했습니다."); + return; + } + sggList = list; + + const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [ + "ognzSn", + "admdstCd", + "admdstNm", + "__isNewArticle", + ]); + + const saveList = noApplicable + ? [] + : rowsForSave.map((row) => + buildPublicInstitutionIndustrySaveItem(row, list), + ); + const deleteList = (deleteListByTab.get(tabKey) ?? []) + .filter((row) => + isPersistedPublicInstitutionArticleRow( + tabKey, + row, + persistedArticleOgnzSnByTabRef, + preYearArticleOgnzSnByTabRef, + ), + ) + .map((row) => buildPublicInstitutionIndustrySaveItem(row, list)); + + if (!noApplicable && saveList.length === 0) { + alert("해당사항 없음을 체크해주세요."); + return; + } + + response = + tabKey === PublicInstitutionSewerIndustryInpColumn.tabKey + ? await statisticsInputSewageExcretaRelatedApi.savePublicInstitutionSewerIndustry( + { + ...baseSaveRequest, + ...userFields, + saveList, + deleteList, + }, + ) + : await statisticsInputSewageExcretaRelatedApi.savePublicInstitutionExcretaIndustry( + { + ...baseSaveRequest, + ...userFields, + saveList, + deleteList, + }, + ); + } else { + alert("해당 탭은 저장 기능이 준비되지 않았습니다."); + return; + } + + if (response.status === "success") { + if (isPublicInstitutionTab) { + setDeleteListByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, []); + return next; + }); + preYearArticleOgnzSnByTabRef.current.delete(tabKey); + if (sggList.length > 0) { + setSggOptions(sggList); + } + } + + const param = + searchParamRef.current ?? + buildSewageExcretaRelatedSearchParam(lastSearchedValues); + await loadActiveTabData(tabKey, param, lastSearchedValues, false, false); + alert("저장되었습니다."); + } else { + alert(response.message ?? "저장 중 오류가 발생했습니다."); + } + } catch { + alert("저장 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + })(); }; - const addArticleFunc = () => { - // 항목 추가 로직 + const addArticleFunc = async () => { + const ctx = lastSearchedValues ?? searchValues; + if (!hasSearched || !ctx?.sido || ctx.sido === "ALL") { + alert("먼저 조회를 진행해주세요."); + return; + } + if (!buildGugunOkForToolbar(ctx)) { + alert("시군구를 선택해주세요."); + return; + } + + const tabKey = activeTabRef.current; + if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) { + alert("항목을 추가할 수 없는 탭입니다."); + return; + } + + const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + if (noApplicable) { + alert("해당사항 없음이 체크된 상태에서는 항목을 추가할 수 없습니다."); + return; + } + + let list = sggOptions; + if (sggOptionsSidoRef.current !== ctx.sido || list.length === 0) { + list = await loadSggOptions(ctx.sido); + sggOptionsSidoRef.current = ctx.sido; + } + if (list.length === 0) { + alert("시군구 목록을 불러오지 못했습니다."); + return; + } + + const sggNm = ctx.gugun && ctx.gugun !== "ALL" ? ctx.gugun : ""; + commitActiveGrid(); + const currentRows = getTabGridRows(tabKey); + const newRow = createEmptyArticleRow(tabKey, ctx.sido, sggNm); + newRow.__isNewArticle = 1; + if (sggNm) { + const { admdstCd, admdstNm } = resolvePublicInstitutionAdmdst(newRow, list); + newRow.admdstCd = admdstCd; + newRow.admdstNm = admdstNm; + } + + flushSync(() => { + setSggOptions(list); + setRowDataMap((prev) => { + const next = new Map(prev); + next.set(tabKey, [...currentRows, newRow]); + return next; + }); + }); + + commitToolbarSnapshotForLoadedTab( + tabKey, + tabStatusInfoMap.get(tabKey) ?? null, + true, + ctx, + ); + }; + + const getCheckedItemIndexesFromGrid = (rowCount: number): number[] => { + const gridView = getGridRef.current?.gridView as + | { + getCheckedItems?: () => number[]; + getCheckedRows?: () => number[]; + } + | undefined; + + const checked = + (typeof gridView?.getCheckedItems === "function" + ? gridView.getCheckedItems() + : typeof gridView?.getCheckedRows === "function" + ? gridView.getCheckedRows() + : []) ?? []; + + return checked.filter( + (idx) => Number.isInteger(idx) && idx >= 0 && idx < rowCount, + ); }; const deleteArticleFunc = () => { - console.log('save'); - // 항목 제외 로직 + const tabKey = activeTabRef.current; + if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) { + alert("항목을 제외할 수 없는 탭입니다."); + return; + } + + const ctx = lastSearchedValues ?? searchValues; + if (!ctx) { + alert("먼저 조회를 진행해주세요."); + return; + } + if (!buildGugunOkForToolbar(ctx)) { + alert("시군구를 선택해주세요."); + return; + } + + commitActiveGrid(); + const currentRows = getTabGridRows(tabKey); + if (currentRows.length === 0) { + alert("삭제할 데이터가 없습니다."); + return; + } + + const checkedIndexes = getCheckedItemIndexesFromGrid(currentRows.length); + const checkedIndexSet = + checkedIndexes.length > 0 + ? new Set(checkedIndexes) + : new Set( + currentRows + .map((row, idx) => (isCheckedRow(row) ? idx : -1)) + .filter((idx) => idx >= 0), + ); + + const removedRows = currentRows.filter((_, idx) => checkedIndexSet.has(idx)); + const nextRows = currentRows.filter((_, idx) => !checkedIndexSet.has(idx)); + if (removedRows.length === 0) { + alert("삭제할 항목을 선택해주세요."); + return; + } + + const persistedRemovedRows = removedRows.filter((row) => + isPersistedPublicInstitutionArticleRow( + tabKey, + row, + persistedArticleOgnzSnByTabRef, + preYearArticleOgnzSnByTabRef, + ), + ); + if (persistedRemovedRows.length > 0) { + setDeleteListByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, [...(prev.get(tabKey) ?? []), ...persistedRemovedRows]); + return next; + }); + } + + setRowDataMap((prev) => { + const next = new Map(prev); + next.set(tabKey, nextRows); + return next; + }); + + commitToolbarSnapshotForLoadedTab( + tabKey, + tabStatusInfoMap.get(tabKey) ?? null, + nextRows.length > 0, + ctx, + ); }; - const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); + const columnInfoByTab = useMemo(() => { + const map = new Map(); + for (const tab of SewageExcretaRelatedInpTabList) { + const tabKey = tab.tabKey; + const canEdit = + computeTabEnterable( + tabStatusInfoMap.get(tabKey) ?? null, + inputPeriodEnterable, + ) || isAdminInfo; + const rawColumns = (tab.columns ?? []) as ColumnConfig[]; + const base = canEdit + ? rawColumns + : rawColumns.map((col) => ({ ...col, editable: false as const })); + const columns = base.map((col) => { + let next: ColumnConfig = col; + if (col.fieldName === "ctpvNm") { + next = { ...col, editable: false as const, styleName: "rg-cell-readonly" }; + } else if ( + SGG_NM_DROPDOWN_TAB_KEYS.has(tabKey) && + col.fieldName === "sggNm" + ) { + next = buildSggNmColumn(col, canEdit, sggOptions); + } else if (col.fieldName === "sggNm") { + next = { ...col, editable: false as const, styleName: "rg-cell-readonly" }; + } + const guide = columnGuidesByTab[tabKey]?.[col.fieldName]?.trim(); + return guide ? { ...next, tooltip: guide } : next; + }); + map.set(tabKey, { + columns, + columnLayout: tab.columnLayout ?? [], + }); + } + return map; + }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab, sggOptions]); + + const publicInstitutionArticleCellStyleCallback = useCallback( + ({ fieldName, row }) => { + if (fieldName !== "sggNm") return undefined; + if (isNewArticleRow(row)) return undefined; + return { styleName: "rg-cell-readonly", editable: false }; + }, + [], + ); + + const resolveGridCellStyleCallback = useCallback( + (tabKey: string): CellStyleCallback => + (params) => { + const verification = verificationCellStyle(params); + if (!SGG_NM_DROPDOWN_TAB_KEYS.has(tabKey)) { + return verification; + } + return mergeCellStyleResults( + verification, + publicInstitutionArticleCellStyleCallback(params), + ); + }, + [publicInstitutionArticleCellStyleCallback], + ); + + const searchContextForToolbar = lastSearchedValues ?? searchValues; + const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; + const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab); + const activeTabToolbar = + toolbarSnapshotByTab.get(activeTab) ?? + computeTabToolbarSnapshot( + activeTabStatus, + activeTabHasRowData, + hasSearched, + searchContextForToolbar, + inputPeriodEnterable, + isAdminInfo, + ); + + const showSaveInToolbar = activeTabToolbar.showSaveInToolbar && toolbarGroupAllowed; + const showEnterableToolbarButtons = + activeTabToolbar.showEnterableToolbarButtons && toolbarGroupAllowed; + const showActionToolbarCard = + toolbarGroupAllowed && + (activeTabToolbar.showActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab))); + const showArticleEditButtons = + ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons; + const showNotApplicableForActiveTab = + NOT_APPLICABLE_TAB_KEYS.has(activeTab) && + loadedTabKeys.has(activeTab) && + shouldShowNotApplicable(activeTab, activeTabHasRowData, activeTabStatus); + const showSecondaryToolbarCard = + hasSearched && + toolbarGroupAllowed && + (showSaveInToolbar || showEnterableToolbarButtons); + const isNotApplicableForActiveTab = isNotApplicableByTab.get(activeTab) ?? false; + const setIsNotApplicableForActiveTab: Dispatch> = useCallback( + (next) => { + const tabKey = activeTabRef.current; + const current = isNotApplicableByTab.get(tabKey) ?? false; + const resolved = + typeof next === "function" + ? (next as (prevState: boolean) => boolean)(current) + : next; + setIsNotApplicableForTab(tabKey, resolved); + }, + [isNotApplicableByTab, setIsNotApplicableForTab], + ); + const gugunOk = buildGugunOkForToolbar(lastSearchedValues); + + const verificationQueryContext = (() => { + const ctx = lastSearchedValues ?? searchValues; + const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab; + if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; + return { + sewerBzmnCd: ctx.bsnmCd, + ctpvNm: ctx.sido, + sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), + statsYr: ctx.year, + statsCd, + }; + })(); return ( <> - -
-
- - - - -
-
-
-
- - -
-
- + + {showActionToolbarCard && ( +
+
+ + {showEnterableToolbarButtons && ( + <> + void lastYearDataFunc()} + /> + + + )} + {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + + )} + {showSaveInToolbar && } +
+
+ )} + {showSecondaryToolbarCard && ( +
+
+ {showNotApplicableForActiveTab && ( + + )} + {showArticleEditButtons && ( + <> + void addArticleFunc()} /> + + + )} +
+
+ )} + {isLoading && ( +
+
+ +
+
+ )} +
+ void handleActiveTabChange(v)} + className="w-full" + variant="default" + > + + {SewageExcretaRelatedInpTabList.map((info) => ( + + {info.tabLabel || info.tabKey} + + ))} + + {SewageExcretaRelatedInpTabList.map((info) => { + const tabKey = info.tabKey; + const rowData = rowDataMap.get(tabKey) ?? []; + const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; + const tabEnterableForGrid = + computeTabEnterable(tabStatusInfo, inputPeriodEnterable) || + isAdminInfo; + + return ( + + { + setRowDataMap((prev) => { + const next = new Map(prev); + const cur = next.get(tabKey) ?? []; + const nextRows = + typeof updater === "function" + ? updater(cur) + : updater; + next.set(tabKey, nextRows); + return next; + }); + }} + showHeaderCheckbox={ARTICLE_EDIT_TAB_KEYS.has(tabKey)} + fixedColumns={info.fixedColumns} + enableSort={false} + statusInfo={tabStatusInfo} + isExcelImport={ + tabRowDataExists(rowDataMap, tabKey) && + tabEnterableForGrid && + gugunOk + } + isExcelExport + getGridDataRef={ + activeTab === tabKey ? getGridDataRef : undefined + } + getGridRef={activeTab === tabKey ? getGridRef : undefined} + cellStyleCallback={resolveGridCellStyleCallback(tabKey)} + cellTooltipCallback={verificationCellTooltip} + /> + + ); + })} + +
+
+ { + const values = lastSearchedValues ?? searchValues; + if (!values || !searchParamRef.current) return; + await loadActiveTabData( + activeTabRef.current, + searchParamRef.current, + values, + true, + false, + ); + }} + /> + + + { + const values = lastSearchedValues ?? searchValues; + if (!values || !searchParamRef.current) return; + await loadActiveTabData( + activeTabRef.current, + searchParamRef.current, + values, + true, + false, + ); + }} + /> ); }; -export default SewageExcretaRelatedInp; \ No newline at end of file +export default SewageExcretaRelatedInp; diff --git a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx index 6df8ce665..902fea894 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx @@ -1,82 +1,2155 @@ -import { useState } from "react"; +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, + type Dispatch, + type SetStateAction, +} from "react"; + +import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; +import { ValueType } from "realgrid"; +import type { RealGridReact } from "realgrid-react"; +import { flushSync } from "react-dom"; + +import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import SearchForm from "@/components/module/SearchForm"; -import TabDataGrid from "@/components/module/TabDataGrid"; -import SubmitButton from "@/components/button/SumitButton"; -import SaveButton from "@/components/button/SaveButton"; -import SubmitExceptionButton from "@/components/button/SubmitExceptionButton"; -import LastYearDataButton from "@/components/button/LastYearDataButton"; import AddArticleButton from "@/components/button/AddArticleButton"; +import CancelSubmitButton from "@/components/button/CancelSubmitButton"; import DeleteArticleButton from "@/components/button/DeleteArticleButton"; +import LastYearDataButton from "@/components/button/LastYearDataButton"; import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton"; - -import { SlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn"; -import { DigestorOperatingStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn"; -import { SewageSlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn"; +import SaveButton from "@/components/button/SaveButton"; +import SubmitButton from "@/components/button/SumitButton"; +import VerificationResultButton from "@/components/button/VerificationResultButton"; +import DataGrid from "@/components/module/DataGrid"; +import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results"; +import SearchForm from "@/components/module/SearchForm"; +import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; +import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; +import SubmitDailog from "./SubmitDailog"; +import type { + CellEditedCallback, + CellStyleCallback, + CellStyleCallbackParams, + CellStyleCallbackResult, + CellTooltipCallbackParams, + ColumnConfig, + ColumnInfo, +} from "@/components/type/DataGrid.type"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; +import { + DigestorOperatingStatusInpColumn, + DIGESTOR_OPERATING_STATUS_EN_YN_FIELD, + isDigestorOperatingRowDisabled, +} from "@/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn"; import { ExcretaSlurgeProcessingStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/ExcretaSlurgeProcessingStatusInpColumn"; +import { SewageSlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn"; +import { SlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn"; +import { doList } from "@/components/util/column/input/DoList"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { statisticsInputSewageExcretaSlurgeApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service"; +import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; +import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { + SEWAGE_EXCRETA_DIGESTOR_OPERATING_STATS_CD, + SEWAGE_EXCRETA_EXCRETA_SLURGE_PROCESSING_STATS_CD, + SEWAGE_EXCRETA_SEWAGE_SLURGE_FAC_STATUS_STATS_CD, + SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD, +} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface"; +import type { + SewageExcretaSlurgeRequestParams, + DigestorOperatingStatusSaveItem, + ExcretaSlurgeProcessingStatusSaveItem, + SewageExcretaSlurgeVerificationResult, + SewageSlurgeProcessingFacStatusSaveItem, + SlurgeProcessingFacStatusSaveItem, +} from "@/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface"; +import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface"; +import type { ApiResponse, cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, Sgg } from "@/entities/statistics/model/statistics-interface"; +import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; +import { useAdminStore } from "@/store/admin-store"; +import { getUserInfo } from "../../../../../../packages/common/components/src/api"; +type GridRowWithVerification = Record & { + verificationResult?: SewageExcretaSlurgeVerificationResult; +}; + +function mergeCellStyleResults( + ...results: (CellStyleCallbackResult | undefined)[] +): CellStyleCallbackResult | undefined { + const styleNames: string[] = []; + let editable: boolean | undefined; + let readOnly: boolean | undefined; + + for (const result of results) { + if (!result) continue; + if (typeof result === "string") { + styleNames.push(result); + continue; + } + if (result.styleName) styleNames.push(result.styleName); + if (result.editable === false) editable = false; + if (result.readOnly === true) readOnly = true; + } + + const styleName = styleNames.join(" ").trim(); + if (!styleName && editable === undefined && readOnly === undefined) { + return undefined; + } + return { + ...(styleName ? { styleName } : {}), + ...(editable !== undefined ? { editable } : {}), + ...(readOnly !== undefined ? { readOnly } : {}), + }; +} + +function verificationCellStyle({ + fieldName, + row, +}: CellStyleCallbackParams): CellStyleCallbackResult | undefined { + const verification = (row as GridRowWithVerification).verificationResult; + if (!verification) return; + const errors = verification[fieldName]; + if (Array.isArray(errors) && errors.length > 0) { + const hasImr = errors.some((e) => e.vrfcCd === "IMR" || e.vrfcCd === "HB"); + return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error"; + } +} + +function verificationCellTooltip({ + fieldName, + row, +}: CellTooltipCallbackParams): string | undefined { + const verification = (row as GridRowWithVerification).verificationResult; + if (!verification) return; + const errors = verification[fieldName]; + if (Array.isArray(errors) && errors.length > 0) { + return errors + .map((e) => { + const prefix = + e.vrfcCd === "IMR" + ? "IMR검증 - " + : e.vrfcCd === "NV" + ? "일반검증 - " + : "HB검증 - "; + return `${prefix}${e.rsnExpln} +(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; + }) + .filter(Boolean) + .join("\n"); + } +} + +function buildSewageExcretaSlurgeSearchParam( + values: SearchFormValues, + overrides?: { mode?: string }, +): SewageExcretaSlurgeRequestParams { + return { + statsYr: values.year, + sewerBzmnCd: values.bsnmCd, + ctpvNm: values.sido, + sggNm: values.gugun === "ALL" ? "" : values.gugun, + fcltNm: values.fctName?.trim() || undefined, + ...(overrides?.mode ? { mode: overrides.mode } : {}), + }; +} + +function mapListToGridRows( + list: T[] | undefined, + ctpvNm: string, +): GridRowWithVerification[] { + return (list ?? []).map((item) => { + const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification; + if (item.verificationResult) { + row.verificationResult = item.verificationResult; + } + return row; + }); +} + +const SewageExcretaSlurgeInpTabList = [ + SlurgeProcessingFacStatusInpColumn, + DigestorOperatingStatusInpColumn, + SewageSlurgeProcessingFacStatusInpColumn, + ExcretaSlurgeProcessingStatusInpColumn, +] as const; + +const firstTabKey = SlurgeProcessingFacStatusInpColumn.tabKey; + +/** 항목 추가·제외 대상 탭 — ST00110003 (ST00110001·ST00110002·ST00110004 제외) */ +const ARTICLE_EDIT_TAB_KEYS = new Set([ + SewageSlurgeProcessingFacStatusInpColumn.tabKey, +]); + +/** ST00110003 — 시군구(sggNm) 드롭다운 선택 */ +const SGG_NM_DROPDOWN_TAB_KEYS = new Set([ + SewageSlurgeProcessingFacStatusInpColumn.tabKey, +]); + +const EMPTY_PUBLIC_SEWAGE_FACILITIES_MSG = + "06. 공공하수처리시설 데이터가 없으면 전년도 데이터 조회 및 항목 추가가 불가능 합니다."; + +const EMPTY_EXCRETA_TREATMENT_FACILITIES_MSG = + "07. 분뇨처리시설 데이터가 없으면 전년도 데이터 조회가 불가능 합니다."; + +/** ST00110001~ST00110004 — 저장 API 연동 탭 */ +const SAVE_SUPPORTED_TAB_KEYS = new Set([ + SlurgeProcessingFacStatusInpColumn.tabKey, + DigestorOperatingStatusInpColumn.tabKey, + SewageSlurgeProcessingFacStatusInpColumn.tabKey, + ExcretaSlurgeProcessingStatusInpColumn.tabKey, +]); + +function mergeHiddenFieldsForSubmitTab(tabKey: string): string[] { + if ( + tabKey === SlurgeProcessingFacStatusInpColumn.tabKey || + tabKey === DigestorOperatingStatusInpColumn.tabKey + ) { + return ["swtrmtFcltCd", "fcltSn", "admdstCd", "admdstNm"]; + } + if (tabKey === SewageSlurgeProcessingFacStatusInpColumn.tabKey) { + return ["fcltSn", "admdstCd", "admdstNm"]; + } + if (tabKey === ExcretaSlurgeProcessingStatusInpColumn.tabKey) { + return ["fcltSn", "admdstCd", "admdstNm", "fcltPstnCn"]; + } + return []; +} + +/** ST00110001~ST00110004 — 제출 API 연동 탭 */ +const SUBMIT_SUPPORTED_TAB_KEYS = new Set([ + SlurgeProcessingFacStatusInpColumn.tabKey, + DigestorOperatingStatusInpColumn.tabKey, + SewageSlurgeProcessingFacStatusInpColumn.tabKey, + ExcretaSlurgeProcessingStatusInpColumn.tabKey, +]); + +const ARTICLE_TAB_BY_KEY = new Map( + SewageExcretaSlurgeInpTabList.map((tab) => [tab.tabKey, tab]), +); + +function createEmptyArticleRow( + tabKey: string, + ctpvNm: string, + sggNm = "", +): Record { + const row: Record = { ctpvNm, sggNm }; + const columns = (ARTICLE_TAB_BY_KEY.get(tabKey)?.columns ?? []) as ColumnConfig[]; + for (const col of columns) { + const fieldName = col.fieldName; + if (!fieldName || fieldName === "ctpvNm" || fieldName === "sggNm") continue; + row[fieldName] = col.valueType === ValueType.NUMBER ? 0 : ""; + } + return row; +} + +type ArticleFcltSnSet = Set; + +function extractFcltSnSet(rows: Record[]): ArticleFcltSnSet { + const set = new Set(); + for (const row of rows) { + const fcltSn = String(row.fcltSn ?? "").trim(); + if (fcltSn) set.add(fcltSn); + } + return set; +} + +function setArticleFcltSnSet( + storeRef: { current: ArticleFcltSnSet }, + rows: Record[], +) { + storeRef.current = extractFcltSnSet(rows); +} + +function isNewArticleRow(row: Record): boolean { + const v = row.__isNewArticle; + return v === 1 || v === "1"; +} + +function clearSlurgeFacStatusArticleFcltSnTracking( + persistedRef: { current: ArticleFcltSnSet }, + preYearRef: { current: ArticleFcltSnSet }, +) { + persistedRef.current = new Set(); + preYearRef.current = new Set(); +} + +/** + * ST00110001 — deleteList 대상 판별. + * fcltSn만으로는 전년도(PRE)·항목추가 행과 구분 불가 → __isNewArticle + preYear ref 사용. + */ +function isPersistedSlurgeFacStatusArticleRow( + row: Record, + persistedRef: { current: ArticleFcltSnSet }, + preYearRef: { current: ArticleFcltSnSet }, +): boolean { + if (isNewArticleRow(row)) return false; + const fcltSn = String(row.fcltSn ?? "").trim(); + if (!fcltSn) return false; + if (preYearRef.current.has(fcltSn)) return false; + return persistedRef.current.has(fcltSn); +} + +/** ST00110003 등 — 전년도 조회 시 fcltSn 미제공, fcltSn 유무로 판단 */ +function isPersistedArticleRowByFcltSn(row: Record): boolean { + if (isNewArticleRow(row)) return false; + return String(row.fcltSn ?? "").trim() !== ""; +} + +const toGridColumnInfo = (tab: (typeof SewageExcretaSlurgeInpTabList)[number]): ColumnInfo => ({ + columns: tab.columns ?? [], + columnLayout: tab.columnLayout ?? [], +}); + +function tabRowDataExists( + rowDataMap: Map[]>, + tabKey: string, +): boolean { + const rows = rowDataMap.get(tabKey); + return Array.isArray(rows) && rows.length > 0; +} + +type TabToolbarSnapshot = { + showSaveInToolbar: boolean; + showEnterableToolbarButtons: boolean; + showActionToolbarCard: boolean; +}; + +const computeTabEnterable = ( + statusInfo: SewerageStatusInfo | null | undefined, + periodEnterable: boolean, +): boolean => { + if (statusInfo?.laeSbmsnSttsCd) { + if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) { + return false; + } + return periodEnterable; + } + return periodEnterable; +}; + +function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { + return doList.includes(values?.bsnmCd || "") + ? Boolean(values?.gugun && values.gugun !== "ALL") + : true; +} + +function computeTabToolbarSnapshot( + statusInfo: SewerageStatusInfo | null, + hasRowData: boolean, + treatAsSearched: boolean, + searchContext: SearchFormValues | null, + periodEnterable: boolean, + isAdminInfo: boolean, +): TabToolbarSnapshot { + const gugunOk = buildGugunOkForToolbar(searchContext); + const enterable = computeTabEnterable(statusInfo, periodEnterable); + const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2"; + const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk; + const showEnterableToolbarButtons = enterable && gugunOk; + const showSubmittedInInputPeriodToolbar = + isSubmitted && periodEnterable && gugunOk; + const showActionToolbarCard = + (hasRowData || treatAsSearched) && + (showSaveInToolbar || + showEnterableToolbarButtons || + showSubmittedInInputPeriodToolbar || + (isSubmitted && isAdminInfo)); + return { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard }; +} + +function parseSggListFromResponse(res: ApiResponse): Sgg[] { + if (res.status !== "success" || res.data == null) return []; + return Array.isArray(res.data) ? res.data : []; +} + +function buildSggNmColumn( + col: ColumnConfig, + canEdit: boolean, + sggOptions: Sgg[], +): ColumnConfig { + if (!canEdit) { + return { ...col, editable: false as const, styleName: "rg-cell-readonly" }; + } + if (sggOptions.length === 0) { + return { ...col, editable: true, styleName: undefined }; + } + const labels = sggOptions.map((s) => s.gugun); + return { + ...col, + editable: true, + styleName: undefined, + editor: { + type: "dropdown", + domainOnly: true, + textReadOnly: true, + commitOnSelect: true, + dropDownWhenClick: true, + values: labels, + labels, + }, + values: labels, + labels, + lookupDisplay: true, + }; +} + +function buildEmptyDataAlertOverrides(tabKey: string): + | { alertIfDigestorEmpty?: boolean; alertIfExcretaSlurgeEmpty?: boolean } + | undefined { + if (tabKey === DigestorOperatingStatusInpColumn.tabKey) { + return { alertIfDigestorEmpty: true }; + } + if (tabKey === ExcretaSlurgeProcessingStatusInpColumn.tabKey) { + return { alertIfExcretaSlurgeEmpty: true }; + } + return undefined; +} + +function mergeGridRowsWithMapRows( + gridRows: Record[], + mapRows: Record[], + hiddenFields: string[] = [], +): Record[] { + return gridRows.map((gridRow, index) => { + const mapRow = mapRows[index] ?? {}; + const merged: Record = { ...mapRow, ...gridRow }; + if (hiddenFields.length === 0) return merged; + const next = { ...merged }; + for (const field of hiddenFields) { + const fromGrid = String(gridRow[field] ?? "").trim(); + const fromMap = String(mapRow[field] ?? "").trim(); + next[field] = fromGrid || fromMap; + } + return next; + }); +} + +function nullableTrimmedString( + value: string | number | null | undefined, +): string | null { + const s = String(value ?? "").trim(); + return s === "" ? null : s; +} + +function toGridNumber(value: string | number | null | undefined): number { + const n = Number(value); + return Number.isFinite(n) ? n : 0; +} + +function resolveSlurgeFacStatusAdmdst( + row: Record, + sggOptions: Sgg[], +): { admdstCd: string; admdstNm: string } { + const sggNm = String(row.sggNm ?? "").trim(); + if (sggNm) { + const matched = sggOptions.find((s) => s.gugun === sggNm); + if (matched) { + return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun }; + } + } + const existingCd = String(row.admdstCd ?? "").trim(); + const existingNm = String(row.admdstNm ?? "").trim(); + if (existingCd || existingNm) { + return { admdstCd: existingCd, admdstNm: existingNm || sggNm }; + } + return { admdstCd: "", admdstNm: sggNm }; +} + +/** 그리드 행 → ST00110001 저장/삭제 API 항목 */ +function buildSlurgeProcessingFacStatusSaveItem( + row: Record, + sggOptions: Sgg[], +): SlurgeProcessingFacStatusSaveItem { + const { admdstCd, admdstNm } = resolveSlurgeFacStatusAdmdst(row, sggOptions); + return { + admdstCd, + admdstNm, + swtrmtFcltCd: String(row.swtrmtFcltCd ?? "").trim(), + swtrmtFcltNm: String(row.swtrmtFcltNm ?? "").trim(), + fcltSn: String(row.fcltSn ?? "").trim(), + itslfQnt: toGridNumber(row.itslfQnt), + otsdInflwQnt: toGridNumber(row.otsdInflwQnt), + transfTmplntNm: String(row.transfTmplntNm ?? "").trim(), + transfPrcsQnt: toGridNumber(row.transfPrcsQnt), + srpFncRt: toGridNumber(row.srpFncRt), + itslfTs1RclmQnt: toGridNumber(row.itslfTs1RclmQnt), + itslfTs1IncnrQnt: toGridNumber(row.itslfTs1IncnrQnt), + itslfTs1AltdQnt: toGridNumber(row.itslfTs1AltdQnt), + itslfTs1DryQnt: toGridNumber(row.itslfTs1DryQnt), + itslfTs1FuelQnt: toGridNumber(row.itslfTs1FuelQnt), + itslfTs2CmpstgQnt: toGridNumber(row.itslfTs2CmpstgQnt), + cmpsPrcsFcltBzentyNm: String(row.cmpsPrcsFcltBzentyNm ?? "").trim(), + incnrPrcsLfvlm: toGridNumber(row.incnrPrcsLfvlm), + incnrPrcsPrdctRmtrlQnt: toGridNumber(row.incnrPrcsPrdctRmtrlQnt), + incnrPrcsFmlyrQnt: toGridNumber(row.incnrPrcsFmlyrQnt), + incnrPrcsFcltNm: String(row.incnrPrcsFcltNm ?? "").trim(), + stbznPrcsLfvlm: toGridNumber(row.stbznPrcsLfvlm), + stbznPrcsFmlyrQnt: toGridNumber(row.stbznPrcsFmlyrQnt), + stbznPrcsFcltNm: String(row.stbznPrcsFcltNm ?? "").trim(), + dryPrcsLfvlm: toGridNumber(row.dryPrcsLfvlm), + dryPrcsIncnrQnt: toGridNumber(row.dryPrcsIncnrQnt), + dryPrcsFuelQnt: toGridNumber(row.dryPrcsFuelQnt), + dryPrcsPrdctRmtrlQnt: toGridNumber(row.dryPrcsPrdctRmtrlQnt), + dryPrcsFmlyrQnt: toGridNumber(row.dryPrcsFmlyrQnt), + dryPrcsCmpstgQnt: toGridNumber(row.dryPrcsCmpstgQnt), + dryPrcsFcltNm: String(row.dryPrcsFcltNm ?? "").trim(), + fuelPrcsIncnrQnt: toGridNumber(row.fuelPrcsIncnrQnt), + fuelPrcsFuelQnt: toGridNumber(row.fuelPrcsFuelQnt), + fuelPrcsFcltNm: String(row.fuelPrcsFcltNm ?? "").trim(), + otsdCnsgnRclmQnt: toGridNumber(row.otsdCnsgnRclmQnt), + otsdCnsgnIncnrQnt: toGridNumber(row.otsdCnsgnIncnrQnt), + otsdCnsgnFuelPwplQnt: toGridNumber(row.otsdCnsgnFuelPwplQnt), + otsdCnsgnPrdctRmtrlQnt: toGridNumber(row.otsdCnsgnPrdctRmtrlQnt), + otsdCnsgnFmlyrQnt: toGridNumber(row.otsdCnsgnFmlyrQnt), + otsdCnsgnCmpstgQnt: toGridNumber(row.otsdCnsgnCmpstgQnt), + otsdCnsgnFcltNm: String(row.otsdCnsgnFcltNm ?? "").trim(), + cparTs1Whol: toGridNumber(row.cparTs1Whol), + rmrk: nullableTrimmedString(row.rmrk), + }; +} + +/** 그리드 행 → ST00110002 저장 API 항목 */ +function buildDigestorOperatingStatusSaveItem( + row: Record, + sggOptions: Sgg[], +): DigestorOperatingStatusSaveItem { + const { admdstCd, admdstNm } = resolveSlurgeFacStatusAdmdst(row, sggOptions); + const base = { + admdstCd, + admdstNm, + swtrmtFcltCd: String(row.swtrmtFcltCd ?? "").trim(), + swtrmtFcltNm: String(row.swtrmtFcltNm ?? "").trim(), + fcltSn: String(row.fcltSn ?? "").trim(), + digstnEnYn: String(row.digstnEnYn ?? "").trim(), + }; + + if (isDigestorOperatingRowDisabled(row)) { + return { + ...base, + digstnEnYn: "N", + digstnCpct: 0, + actlDrvnCpct: 0, + digstnGasInputQnt: 0, + digstnGasInputSldDnst: 0, + digstnTnkOprtngDayCnt: 0, + ocrnGasQnt: 0, + digstnGasUtztnNtslQnt: 0, + digstnGasUtztnEtgtQnt: 0, + digstnGasUtztnItslfQnt: 0, + digstnGasUtztnEtcQnt: 0, + surplsGasQnt: 0, + rmrk: null, + }; + } + + return { + ...base, + digstnCpct: toGridNumber(row.digstnCpct), + actlDrvnCpct: toGridNumber(row.actlDrvnCpct), + digstnGasInputQnt: toGridNumber(row.digstnGasInputQnt), + digstnGasInputSldDnst: toGridNumber(row.digstnGasInputSldDnst), + digstnTnkOprtngDayCnt: toGridNumber(row.digstnTnkOprtngDayCnt), + ocrnGasQnt: toGridNumber(row.ocrnGasQnt), + digstnGasUtztnNtslQnt: toGridNumber(row.digstnGasUtztnNtslQnt), + digstnGasUtztnEtgtQnt: toGridNumber(row.digstnGasUtztnEtgtQnt), + digstnGasUtztnItslfQnt: toGridNumber(row.digstnGasUtztnItslfQnt), + digstnGasUtztnEtcQnt: toGridNumber(row.digstnGasUtztnEtcQnt), + surplsGasQnt: toGridNumber(row.surplsGasQnt), + rmrk: nullableTrimmedString(row.rmrk), + }; +} + +/** 그리드 행 → ST00110003 저장/삭제 API 항목 */ +function buildSewageSlurgeProcessingFacStatusSaveItem( + row: Record, + sggOptions: Sgg[], +): SewageSlurgeProcessingFacStatusSaveItem { + const { admdstCd, admdstNm } = resolveSlurgeFacStatusAdmdst(row, sggOptions); + return { + admdstCd, + admdstNm, + fcltSn: String(row.fcltSn ?? "").trim(), + fcltNm: String(row.fcltNm ?? "").trim(), + prcsMthExpln: String(row.prcsMthExpln ?? "").trim(), + fcltCpct: toGridNumber(row.fcltCpct), + anlPrcsQnt: toGridNumber(row.anlPrcsQnt), + dayAvgOprtngHr: toGridNumber(row.dayAvgOprtngHr), + anlOprtngDayCnt: toGridNumber(row.anlOprtngDayCnt), + rmrk: nullableTrimmedString(row.rmrk), + }; +} + +/** 그리드 행 → ST00110004 저장 API 항목 */ +function buildExcretaSlurgeProcessingStatusSaveItem( + row: Record, + sggOptions: Sgg[], +): ExcretaSlurgeProcessingStatusSaveItem { + const { admdstCd, admdstNm } = resolveSlurgeFacStatusAdmdst(row, sggOptions); + return { + admdstCd, + admdstNm, + fcltSn: String(row.fcltSn ?? "").trim(), + fcltNm: String(row.fcltNm ?? "").trim(), + fcltPstnCn: String(row.fcltPstnCn ?? "").trim(), + rcycQnt: toGridNumber(row.rcycQnt), + olandLfvlm: toGridNumber(row.olandLfvlm), + incnrQnt: toGridNumber(row.incnrQnt), + oceanInputQnt: toGridNumber(row.oceanInputQnt), + fuelQnt: toGridNumber(row.fuelQnt), + etcQnt: toGridNumber(row.etcQnt), + nondspsQnt: toGridNumber(row.nondspsQnt), + swtrmtFcltNm: String(row.swtrmtFcltNm ?? "").trim(), + rmrk: nullableTrimmedString(row.rmrk), + }; +} + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +async function waitForImrJobCompleted( + jobId: string, + getStatus: (id: string) => Promise, + options?: { + pollIntervalMs?: number; + maxAttempts?: number; + onProgress?: (jobProgress: number) => void; + }, +): Promise { + 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 검증 완료 대기 시간이 초과되었습니다."); +} // 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리 const SewageExcretaSlurgeInp = () => { - - // tab list - const SewageExcretaSlurgeInpTabList = [ - SlurgeProcessingFacStatusInpColumn, - DigestorOperatingStatusInpColumn, - SewageSlurgeProcessingFacStatusInpColumn, - ExcretaSlurgeProcessingStatusInpColumn - ]; + const { isAdminInfo } = useAdminStore(); + const { confirm } = useConfirm(); + const { setMessage: alert } = useAlertStore((state) => state); + const toolbarGroupAllowed = useStatisticsInputToolbarGroupGate(); + 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 submitExceptionFunc = () => { - // 예외 제출 로직 + const [isLoading, setIsLoading] = useState(false); + const [activeTab, setActiveTab] = useState(firstTabKey); + const [rowDataMap, setRowDataMap] = useState< + Map[]> + >(new Map()); + const [isNotApplicableByTab, setIsNotApplicableByTab] = useState>( + () => new Map(), + ); + const [searchValues, setSearchValues] = useState(null); + const [lastSearchedValues, setLastSearchedValues] = useState(null); + const [hasSearched, setHasSearched] = useState(false); + const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); + const [tabStatusInfoMap, setTabStatusInfoMap] = useState< + Map + >(new Map()); + const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< + Map + >(new Map()); + const [sggOptions, setSggOptions] = useState([]); + const [loadedTabKeys, setLoadedTabKeys] = useState>(() => new Set()); + const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); + const [showSubmitDialog, setShowSubmitDialog] = useState(false); + const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false); + const [deleteListByTab, setDeleteListByTab] = useState< + Map[]> + >(() => new Map()); + const [columnGuidesByTab, setColumnGuidesByTab] = useState< + Record> + >({}); + + const searchParamRef = useRef(null); + const searchDoneRef = useRef(false); + const periodEnterableRef = useRef(true); + const loadedTabKeysRef = useRef>(new Set()); + const sggOptionsSidoRef = useRef(null); + /** ST00110002 당해 조회 결과 유무(공공하수처리시설 연동) — 전년도 조회 가능 여부 */ + const publicSewageFacilitiesAvailableRef = useRef(false); + /** ST00110004 당해 조회 결과 유무(분뇨처리시설 연동 데이터) — 전년도 조회 가능 여부 */ + const excretaTreatmentFacilitiesAvailableRef = useRef(false); + const activeTabRef = useRef(activeTab); + const getGridDataRef = useRef<(() => Record[]) | null>(null); + const getGridRef = useRef(null); + /** ST00110001 — 당해 연도 조회 API로 로드된 fcltSn (deleteList 대상) */ + const persistedArticleFcltSnRef = useRef(new Set()); + /** ST00110001 — 전년도(PRE) 조회로 로드된 fcltSn */ + const preYearArticleFcltSnRef = useRef(new Set()); + activeTabRef.current = activeTab; + + useEffect(() => { + let cancelled = false; + void (async () => { + const entries = await Promise.all( + SewageExcretaSlurgeInpTabList.map(async (tab) => { + const tabKey = tab.tabKey; + try { + const res = await statisticsApi.getColumnGuides(tabKey); + if (res.status !== "success" || !Array.isArray(res.data)) { + return [tabKey, {}] as const; + } + const next: Record = {}; + for (const item of res.data) { + const key = item.engColNm?.trim(); + const guide = item.columnGuide?.trim(); + if (key && guide) next[key] = guide; + } + return [tabKey, next] as const; + } catch { + return [tabKey, {}] as const; + } + }), + ); + if (cancelled) return; + setColumnGuidesByTab(Object.fromEntries(entries)); + })(); + return () => { + cancelled = true; + }; + }, []); + + const shouldShowNotApplicable = ( + hasRows: boolean, + statusInfo: SewerageStatusInfo | null | undefined, + ): boolean => + hasSearched && + !hasRows && + computeTabEnterable(statusInfo, inputPeriodEnterable); + + const setIsNotApplicableForTab = useCallback((tabKey: string, nextValue: boolean) => { + setIsNotApplicableByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, nextValue); + return next; + }); + }, []); + + const loadSggOptions = useCallback(async (sido: string): Promise => { + if (!sido || sido === "ALL") { + setSggOptions([]); + return []; + } + try { + const res = await statisticsApi.getSggAllList(sido); + const list = parseSggListFromResponse(res); + setSggOptions(list); + return list; + } catch { + setSggOptions([]); + return []; + } + }, []); + + const markTabLoaded = (tabKey: string) => { + loadedTabKeysRef.current.add(tabKey); + setLoadedTabKeys(new Set(loadedTabKeysRef.current)); }; - const lastYearDataFunc = () => { - // 지난연도 데이터 가져오기 로직 + const commitToolbarSnapshotForLoadedTab = ( + tabKey: string, + statusInfo: SewerageStatusInfo | null, + hasRowData: boolean, + searchContext: SearchFormValues, + ) => { + setToolbarSnapshotByTab((prev) => { + const next = new Map(prev); + next.set( + tabKey, + computeTabToolbarSnapshot( + statusInfo, + hasRowData, + searchDoneRef.current, + searchContext, + periodEnterableRef.current, + isAdminInfo, + ), + ); + return next; + }); + }; + + const getTabGridRows = (tabKey: string): Record[] => + tabKey === activeTabRef.current + ? (getGridDataRef.current?.() ?? rowDataMap.get(tabKey) ?? []) + : (rowDataMap.get(tabKey) ?? []); + + const commitActiveGrid = () => { + const gridView = getGridRef.current?.gridView as + | { commit?: (force?: boolean) => void; cancel?: () => void } + | undefined; + try { + gridView?.commit?.(true); + } catch { + gridView?.cancel?.(); + } + }; + + const handleActiveTabChange = async (tabKey: string) => { + const prevTabKey = activeTabRef.current; + commitActiveGrid(); + const latestRowsInPrevTab = getTabGridRows(prevTabKey); + if (latestRowsInPrevTab.length > 0 || rowDataMap.has(prevTabKey)) { + setRowDataMap((prev) => { + const next = new Map(prev); + next.set(prevTabKey, latestRowsInPrevTab); + return next; + }); + } + + flushSync(() => { + activeTabRef.current = tabKey; + setActiveTab(tabKey); + }); + + if (!hasSearched || !lastSearchedValues || !searchParamRef.current) return; + + if (loadedTabKeysRef.current.has(tabKey)) return; + + try { + setIsLoading(true); + await loadActiveTabData( + tabKey, + searchParamRef.current, + lastSearchedValues, + true, + false, + buildEmptyDataAlertOverrides(tabKey), + ); + } catch { + alert("데이터 조회 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + }; + + const applyRowMap = ( + mergeIntoExistingMap: boolean, + apply: (m: Map[]>) => void, + ) => { + if (mergeIntoExistingMap) { + setRowDataMap((prev) => { + const next = new Map(prev); + apply(next); + return next; + }); + } else { + const next = new Map[]>(); + apply(next); + setRowDataMap(next); + } + }; + + const syncSlurgeFacStatusArticleFcltSnTracking = ( + gridRows: Record[], + fromPreYear: boolean, + ) => { + if (fromPreYear) { + setArticleFcltSnSet(preYearArticleFcltSnRef, gridRows); + return; + } + setArticleFcltSnSet(persistedArticleFcltSnRef, gridRows); + }; + + const isPersistedArticleRowForTab = useCallback( + (tabKey: string, row: Record) => { + if (tabKey === SlurgeProcessingFacStatusInpColumn.tabKey) { + return isPersistedSlurgeFacStatusArticleRow( + row, + persistedArticleFcltSnRef, + preYearArticleFcltSnRef, + ); + } + return isPersistedArticleRowByFcltSn(row); + }, + [], + ); + + const loadActiveTabData = async ( + tabKey: string, + param: SewageExcretaSlurgeRequestParams, + searchContext: SearchFormValues, + mergeIntoExistingMap: boolean, + updateLastSearchedValues: boolean, + overrides?: { + mode?: string; + alertIfDigestorEmpty?: boolean; + alertIfExcretaSlurgeEmpty?: boolean; + }, + ): Promise => { + const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param; + const fromPreYear = overrides?.mode === "PRE"; + + setIsNotApplicableForTab(tabKey, false); + + if (tabKey === SlurgeProcessingFacStatusInpColumn.tabKey) { + const response = + await statisticsInputSewageExcretaSlurgeApi.getSlurgeProcessingFacStatusList( + requestParam, + ); + if (response.status !== "success" || !response.data) { + alert(response.message || "데이터 조회 중 오류가 발생했습니다."); + return false; + } + const { commonInfo, statusInfo, slurgeProcessingFacStatusList } = response.data; + const gridRows = mapListToGridRows(slurgeProcessingFacStatusList, commonInfo.ctpvNm); + syncSlurgeFacStatusArticleFcltSnTracking(gridRows, fromPreYear); + applyRowMap(mergeIntoExistingMap, (m) => { + m.set(SlurgeProcessingFacStatusInpColumn.tabKey, gridRows); + }); + setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); + setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + if (updateLastSearchedValues) setLastSearchedValues(searchContext); + commitToolbarSnapshotForLoadedTab( + tabKey, + statusInfo, + gridRows.length > 0, + searchContext, + ); + markTabLoaded(tabKey); + return true; + } + + if (tabKey === DigestorOperatingStatusInpColumn.tabKey) { + const response = + await statisticsInputSewageExcretaSlurgeApi.getDigestorOperatingStatusList( + requestParam, + ); + if (response.status !== "success" || !response.data) { + alert(response.message || "데이터 조회 중 오류가 발생했습니다."); + return false; + } + const { commonInfo, statusInfo, digestorOperatingStatusList } = response.data; + const gridRows = mapListToGridRows(digestorOperatingStatusList, commonInfo.ctpvNm); + if (!fromPreYear) { + publicSewageFacilitiesAvailableRef.current = gridRows.length > 0; + if (gridRows.length === 0 && overrides?.alertIfDigestorEmpty) { + alert(EMPTY_PUBLIC_SEWAGE_FACILITIES_MSG); + } + } + applyRowMap(mergeIntoExistingMap, (m) => { + m.set(DigestorOperatingStatusInpColumn.tabKey, gridRows); + }); + setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); + setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + if (updateLastSearchedValues) setLastSearchedValues(searchContext); + commitToolbarSnapshotForLoadedTab( + tabKey, + statusInfo, + gridRows.length > 0, + searchContext, + ); + markTabLoaded(tabKey); + return true; + } + + if (tabKey === SewageSlurgeProcessingFacStatusInpColumn.tabKey) { + const response = + await statisticsInputSewageExcretaSlurgeApi.getSewageSlurgeProcessingFacStatusList( + requestParam, + ); + if (response.status !== "success" || !response.data) { + alert(response.message || "데이터 조회 중 오류가 발생했습니다."); + return false; + } + const { commonInfo, statusInfo, sewageSlurgeProcessingFacStatusList } = + response.data; + const gridRows = mapListToGridRows( + sewageSlurgeProcessingFacStatusList, + commonInfo.ctpvNm, + ); + applyRowMap(mergeIntoExistingMap, (m) => { + m.set(SewageSlurgeProcessingFacStatusInpColumn.tabKey, gridRows); + }); + setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); + setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + if (updateLastSearchedValues) setLastSearchedValues(searchContext); + commitToolbarSnapshotForLoadedTab( + tabKey, + statusInfo, + gridRows.length > 0, + searchContext, + ); + markTabLoaded(tabKey); + return true; + } + + if (tabKey === ExcretaSlurgeProcessingStatusInpColumn.tabKey) { + const response = + await statisticsInputSewageExcretaSlurgeApi.getExcretaSlurgeProcessingStatusList( + requestParam, + ); + if (response.status !== "success" || !response.data) { + alert(response.message || "데이터 조회 중 오류가 발생했습니다."); + return false; + } + const { commonInfo, statusInfo, excretaSlurgeProcessingStatusList } = response.data; + const gridRows = mapListToGridRows( + excretaSlurgeProcessingStatusList, + commonInfo.ctpvNm, + ); + if (!fromPreYear) { + excretaTreatmentFacilitiesAvailableRef.current = gridRows.length > 0; + if (gridRows.length === 0 && overrides?.alertIfExcretaSlurgeEmpty) { + alert(EMPTY_EXCRETA_TREATMENT_FACILITIES_MSG); + } + } + applyRowMap(mergeIntoExistingMap, (m) => { + m.set(ExcretaSlurgeProcessingStatusInpColumn.tabKey, gridRows); + }); + setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); + setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + if (updateLastSearchedValues) setLastSearchedValues(searchContext); + commitToolbarSnapshotForLoadedTab( + tabKey, + statusInfo, + gridRows.length > 0, + searchContext, + ); + markTabLoaded(tabKey); + return true; + } + + alert("해당 탭 조회 API가 없습니다."); + return false; + }; + + const handleReset = () => { + setRowDataMap(new Map()); + setTabStatusInfoMap(new Map()); + setTabStatsCdMap(new Map()); + setToolbarSnapshotByTab(new Map()); + setActiveTab(firstTabKey); + activeTabRef.current = firstTabKey; + setLastSearchedValues(null); + setHasSearched(false); + setInputPeriodEnterable(true); + searchParamRef.current = null; + setIsNotApplicableByTab(new Map()); + searchDoneRef.current = false; + periodEnterableRef.current = true; + loadedTabKeysRef.current.clear(); + setLoadedTabKeys(new Set()); + setSggOptions([]); + publicSewageFacilitiesAvailableRef.current = false; + excretaTreatmentFacilitiesAvailableRef.current = false; + setDeleteListByTab(new Map()); + clearSlurgeFacStatusArticleFcltSnTracking( + persistedArticleFcltSnRef, + preYearArticleFcltSnRef, + ); + sggOptionsSidoRef.current = null; + }; + + const handleSearch = async (values: SearchFormValues) => { + if (!values.sido || values.sido === "ALL") { + alert("시도를 선택해주세요."); + return; + } + + try { + const periodResponse = await statisticsInputSewSupplyRateApi.checkInputPeriod( + values.year || "", + ); + if (periodResponse.status !== "success") { + alert(periodResponse.message); + return; + } + const periods = Array.isArray(periodResponse.data) ? periodResponse.data : []; + const periodOk = isInputPeriodCurrent(periods); + periodEnterableRef.current = periodOk; + setInputPeriodEnterable(periodOk); + } catch { + alert("입력 기간 확인 중 오류가 발생했습니다."); + return; + } + + const yearChangedFromLastSearch = + lastSearchedValues != null && + (lastSearchedValues.year ?? "") !== (values.year ?? ""); + + if (yearChangedFromLastSearch) { + setRowDataMap(new Map()); + setTabStatusInfoMap(new Map()); + setTabStatsCdMap(new Map()); + setToolbarSnapshotByTab(new Map()); + loadedTabKeysRef.current.clear(); + setLoadedTabKeys(new Set()); + setDeleteListByTab(new Map()); + clearSlurgeFacStatusArticleFcltSnTracking( + persistedArticleFcltSnRef, + preYearArticleFcltSnRef, + ); + activeTabRef.current = firstTabKey; + setActiveTab(firstTabKey); + } + + try { + setIsLoading(true); + searchDoneRef.current = true; + setHasSearched(true); + const searchParam = buildSewageExcretaSlurgeSearchParam(values); + searchParamRef.current = searchParam; + + const loadedSgg = await loadSggOptions(values.sido); + sggOptionsSidoRef.current = values.sido; + if (loadedSgg.length === 0) { + alert("시군구 목록을 불러오지 못했습니다."); + return; + } + + const ok = await loadActiveTabData( + activeTabRef.current, + searchParam, + values, + !yearChangedFromLastSearch, + true, + buildEmptyDataAlertOverrides(activeTabRef.current), + ); + if (!ok) return; + } catch { + alert("데이터 조회 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + }; + + const lastYearDataFunc = async () => { + const values = lastSearchedValues ?? searchValues; + if (!values?.sido || values.sido === "ALL") { + alert("시도를 선택해주세요."); + return; + } + if (!hasSearched) { + alert("먼저 데이터를 조회해주세요."); + return; + } + if ( + activeTabRef.current === DigestorOperatingStatusInpColumn.tabKey && + !publicSewageFacilitiesAvailableRef.current + ) { + alert(EMPTY_PUBLIC_SEWAGE_FACILITIES_MSG); + return; + } + if ( + activeTabRef.current === ExcretaSlurgeProcessingStatusInpColumn.tabKey && + !excretaTreatmentFacilitiesAvailableRef.current + ) { + alert(EMPTY_EXCRETA_TREATMENT_FACILITIES_MSG); + return; + } + + try { + setIsLoading(true); + const param = buildSewageExcretaSlurgeSearchParam(values, { mode: "PRE" }); + await loadActiveTabData( + activeTabRef.current, + param, + values, + true, + false, + { mode: "PRE" }, + ); + } catch { + alert("데이터 조회 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } }; const submitFunc = () => { - // 제출 로직 + void (async () => { + const tabKey = activeTabRef.current; + if (!SUBMIT_SUPPORTED_TAB_KEYS.has(tabKey)) { + alert("해당 탭은 제출 기능이 준비되지 않았습니다."); + return; + } + if (!lastSearchedValues || !searchParamRef.current) { + alert("먼저 조회를 진행해주세요."); + return; + } + if (!buildGugunOkForToolbar(lastSearchedValues)) { + alert("시군구를 선택해주세요."); + return; + } + + commitActiveGrid(); + const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const currentData = getTabGridRows(tabKey); + if (!noApplicable && currentData.length === 0) { + alert("해당사항 없음을 체크해주세요."); + return; + } + + if (await confirm("제출 하시겠습니까?")) { + setShowSubmitDialog(true); + } + })(); + }; + + const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => { + const tabKey = activeTabRef.current; + if (!SUBMIT_SUPPORTED_TAB_KEYS.has(tabKey)) { + alert("해당 탭은 제출 기능이 준비되지 않았습니다."); + return; + } + if (!lastSearchedValues || !searchParamRef.current) { + alert("먼저 조회를 진행해주세요."); + return; + } + if (!buildGugunOkForToolbar(lastSearchedValues)) { + alert("시군구를 선택해주세요."); + return; + } + + commitActiveGrid(); + + const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const currentData = getTabGridRows(tabKey); + const mapRows = rowDataMap.get(tabKey) ?? []; + + const sido = lastSearchedValues.sido ?? ""; + let list = sggOptions; + if (sggOptionsSidoRef.current !== sido || list.length === 0) { + list = await loadSggOptions(sido); + sggOptionsSidoRef.current = sido; + } + if (list.length === 0) { + alert("시군구 목록을 불러오지 못했습니다."); + return; + } + + const isSlurgeFacTab = tabKey === SlurgeProcessingFacStatusInpColumn.tabKey; + const isDigestorTab = tabKey === DigestorOperatingStatusInpColumn.tabKey; + const isSewageSlurgeFacTab = tabKey === SewageSlurgeProcessingFacStatusInpColumn.tabKey; + const isExcretaSlurgeTab = tabKey === ExcretaSlurgeProcessingStatusInpColumn.tabKey; + const rowsForSubmit = mergeGridRowsWithMapRows( + currentData, + mapRows, + mergeHiddenFieldsForSubmitTab(tabKey), + ); + + const slurgeSubmitList = + noApplicable || !isSlurgeFacTab + ? [] + : rowsForSubmit.map((row) => buildSlurgeProcessingFacStatusSaveItem(row, list)); + const digestorSubmitList = + noApplicable || !isDigestorTab + ? [] + : rowsForSubmit.map((row) => buildDigestorOperatingStatusSaveItem(row, list)); + const sewageSlurgeFacSubmitList = + noApplicable || !isSewageSlurgeFacTab + ? [] + : rowsForSubmit.map((row) => + buildSewageSlurgeProcessingFacStatusSaveItem(row, list), + ); + const excretaSlurgeSubmitList = + noApplicable || !isExcretaSlurgeTab + ? [] + : rowsForSubmit.map((row) => + buildExcretaSlurgeProcessingStatusSaveItem(row, list), + ); + const slurgeDeleteList = isSlurgeFacTab + ? (deleteListByTab.get(tabKey) ?? []) + .filter((row) => isPersistedArticleRowForTab(tabKey, row)) + .map((row) => buildSlurgeProcessingFacStatusSaveItem(row, list)) + : []; + const sewageSlurgeFacDeleteList = isSewageSlurgeFacTab + ? (deleteListByTab.get(tabKey) ?? []) + .filter((row) => isPersistedArticleRowForTab(tabKey, row)) + .map((row) => buildSewageSlurgeProcessingFacStatusSaveItem(row, list)) + : []; + + if ( + !noApplicable && + ((isSlurgeFacTab && slurgeSubmitList.length === 0) || + (isDigestorTab && digestorSubmitList.length === 0) || + (isSewageSlurgeFacTab && sewageSlurgeFacSubmitList.length === 0) || + (isExcretaSlurgeTab && excretaSlurgeSubmitList.length === 0)) + ) { + alert("해당사항 없음을 체크해주세요."); + return; + } + + const activeTabStatus = tabStatusInfoMap.get(tabKey) ?? null; + const statsCdForSubmit = + tabStatsCdMap.get(tabKey) ?? + (isDigestorTab + ? SEWAGE_EXCRETA_DIGESTOR_OPERATING_STATS_CD + : isSewageSlurgeFacTab + ? SEWAGE_EXCRETA_SEWAGE_SLURGE_FAC_STATUS_STATS_CD + : isExcretaSlurgeTab + ? SEWAGE_EXCRETA_EXCRETA_SLURGE_PROCESSING_STATS_CD + : SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD); + + const verificationQuery = { + sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ctpvNm: lastSearchedValues.sido ?? "", + sggNm: + lastSearchedValues.gugun === "ALL" + ? "" + : (lastSearchedValues.gugun ?? ""), + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForSubmit, + }; + + const reloadAfterSubmit = async () => { + if (isSlurgeFacTab || isSewageSlurgeFacTab) { + setDeleteListByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, []); + return next; + }); + if (isSlurgeFacTab) { + preYearArticleFcltSnRef.current = new Set(); + } + } + setSggOptions(list); + await loadActiveTabData( + tabKey, + searchParamRef.current!, + lastSearchedValues, + false, + false, + ); + }; + + const submitRequestBase = { + statsYr: lastSearchedValues.year ?? "", + ctpvNm: lastSearchedValues.sido ?? "", + sggNm: + lastSearchedValues.gugun === "ALL" + ? "" + : (lastSearchedValues.gugun ?? ""), + sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + userId: "", + laePicNm: submitInfo.laePicNm, + laePicTelNo: submitInfo.laePicTelNo, + laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, + laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, + laeIdfrNm: submitInfo.laeIdfrNm, + laeIdfrTelNo: submitInfo.laeIdfrTelNo, + laeIdfrDeptNm: submitInfo.laeIdfrDeptNm, + laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm, + ...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}), + noApplicable, + }; + + try { + showGlobalProgress({ label: "서버에 요청 중...", progress: 0 }); + const userInfo = await getUserInfo(); + setGlobalProgressLabel("일반 검증 진행 중..."); + const response = isSlurgeFacTab + ? await statisticsInputSewageExcretaSlurgeApi.submitSlurgeProcessingFacStatus({ + ...submitRequestBase, + userId: userInfo.userUnqId, + submitList: slurgeSubmitList, + deleteList: slurgeDeleteList, + }) + : isDigestorTab + ? await statisticsInputSewageExcretaSlurgeApi.submitDigestorOperatingStatus({ + ...submitRequestBase, + userId: userInfo.userUnqId, + submitList: digestorSubmitList, + }) + : isSewageSlurgeFacTab + ? await statisticsInputSewageExcretaSlurgeApi.submitSewageSlurgeProcessingFacStatus( + { + ...submitRequestBase, + userId: userInfo.userUnqId, + submitList: sewageSlurgeFacSubmitList, + deleteList: sewageSlurgeFacDeleteList, + }, + ) + : await statisticsInputSewageExcretaSlurgeApi.submitExcretaSlurgeProcessingStatus( + { + ...submitRequestBase, + userId: userInfo.userUnqId, + submitList: excretaSlurgeSubmitList, + }, + ); + + 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: statsCdForSubmit, + 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 reloadAfterSubmit(); + await openVerificationResultDialogIfNeeded( + verificationQuery, + setVerificationResultDialogOpen, + { notify: alert }, + ); + } catch (e) { + await reloadAfterSubmit(); + await openVerificationResultDialogIfNeeded( + verificationQuery, + setVerificationResultDialogOpen, + { notify: alert, notifySubmitComplete: false }, + ); + const msg = + e instanceof Error && e.message + ? e.message + : "IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요."; + alert(msg); + } + } else { + setGlobalProgress(100); + await reloadAfterSubmit(); + await openVerificationResultDialogIfNeeded( + verificationQuery, + setVerificationResultDialogOpen, + { notify: alert }, + ); + } + } catch { + alert("데이터 제출 중 오류가 발생했습니다."); + } finally { + hideGlobalProgress(); + setShowSubmitDialog(false); + } + }; + + const cnacelSubmitFunc = () => { + void (async () => { + try { + if (!(await confirm("제출된 내용을 취소하시겠습니까?"))) return; + + if (!lastSearchedValues || !searchParamRef.current) { + alert("먼저 조회를 진행해주세요."); + return; + } + + const userInfo = await getUserInfo(); + const tabKey = activeTabRef.current; + const statsCdForTab = tabStatsCdMap.get(tabKey); + if (!statsCdForTab) { + alert("먼저 조회를 진행해주세요."); + return; + } + + const requestData: cancelSubmitRequest = { + sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ctpvNm: lastSearchedValues.sido ?? "", + sggNm: + lastSearchedValues.gugun === "ALL" + ? "" + : (lastSearchedValues.gugun ?? ""), + statsYr: lastSearchedValues.year ?? "", + mdfrId: userInfo.userUnqId, + statsCd: statsCdForTab, + }; + + const response = await statisticsApi.cancelSubmit(requestData); + + if (response.status === "success") { + setIsLoading(true); + await loadActiveTabData( + tabKey, + searchParamRef.current, + lastSearchedValues, + false, + false, + ); + alert("제출 취소되었습니다."); + } else { + alert(response.message ?? "제출 취소 중 오류가 발생했습니다."); + } + } catch { + alert("제출 취소 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + })(); }; const saveFunc = () => { - // 저장 로직 + void (async () => { + const tabKey = activeTabRef.current; + if (!lastSearchedValues) { + alert("먼저 조회를 진행해주세요."); + return; + } + if (!buildGugunOkForToolbar(lastSearchedValues)) { + alert("시군구를 선택해주세요."); + return; + } + if (!SAVE_SUPPORTED_TAB_KEYS.has(tabKey)) { + alert("해당 탭은 저장 기능이 준비되지 않았습니다."); + return; + } + if (!(await confirm("저장 하시겠습니까?"))) return; + + commitActiveGrid(); + + const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const currentData = getTabGridRows(tabKey); + const mapRows = rowDataMap.get(tabKey) ?? []; + + const sido = lastSearchedValues.sido ?? ""; + let list = sggOptions; + if (sggOptionsSidoRef.current !== sido || list.length === 0) { + list = await loadSggOptions(sido); + sggOptionsSidoRef.current = sido; + } + if (list.length === 0) { + alert("시군구 목록을 불러오지 못했습니다."); + return; + } + + const baseSaveRequest = { + statsYr: lastSearchedValues.year ?? "", + ctpvNm: lastSearchedValues.sido ?? "", + sggNm: + lastSearchedValues.gugun === "ALL" + ? "" + : (lastSearchedValues.gugun ?? ""), + sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...(noApplicable ? { noApplicable: true } : {}), + }; + + try { + setIsLoading(true); + const userInfo = await getUserInfo(); + const userFields = { + userId: userInfo.userUnqId, + ...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}), + }; + + let response: { status: string; message?: string }; + + if (tabKey === SlurgeProcessingFacStatusInpColumn.tabKey) { + const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [ + "swtrmtFcltCd", + "fcltSn", + "admdstCd", + "admdstNm", + ]); + + const saveList = noApplicable + ? [] + : rowsForSave.map((row) => + buildSlurgeProcessingFacStatusSaveItem(row, list), + ); + const deleteList = (deleteListByTab.get(tabKey) ?? []) + .filter((row) => isPersistedArticleRowForTab(tabKey, row)) + .map((row) => buildSlurgeProcessingFacStatusSaveItem(row, list)); + + if (!noApplicable && saveList.length === 0) { + alert("해당사항 없음을 체크해주세요."); + return; + } + + response = + await statisticsInputSewageExcretaSlurgeApi.saveSlurgeProcessingFacStatus( + { + ...baseSaveRequest, + ...userFields, + saveList, + deleteList, + }, + ); + } else if (tabKey === DigestorOperatingStatusInpColumn.tabKey) { + const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [ + "swtrmtFcltCd", + "fcltSn", + "admdstCd", + "admdstNm", + ]); + const saveList = noApplicable + ? [] + : rowsForSave.map((row) => + buildDigestorOperatingStatusSaveItem(row, list), + ); + + if (!noApplicable && saveList.length === 0) { + alert("해당사항 없음을 체크해주세요."); + return; + } + + response = + await statisticsInputSewageExcretaSlurgeApi.saveDigestorOperatingStatus({ + ...baseSaveRequest, + ...userFields, + saveList, + }); + } else if (tabKey === SewageSlurgeProcessingFacStatusInpColumn.tabKey) { + const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [ + "fcltSn", + "admdstCd", + "admdstNm", + ]); + const saveList = noApplicable + ? [] + : rowsForSave.map((row) => + buildSewageSlurgeProcessingFacStatusSaveItem(row, list), + ); + const deleteList = (deleteListByTab.get(tabKey) ?? []) + .filter((row) => isPersistedArticleRowForTab(tabKey, row)) + .map((row) => buildSewageSlurgeProcessingFacStatusSaveItem(row, list)); + + if (!noApplicable && saveList.length === 0) { + alert("해당사항 없음을 체크해주세요."); + return; + } + + response = + await statisticsInputSewageExcretaSlurgeApi.saveSewageSlurgeProcessingFacStatus( + { + ...baseSaveRequest, + ...userFields, + saveList, + deleteList, + }, + ); + } else { + const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [ + "fcltSn", + "admdstCd", + "admdstNm", + "fcltPstnCn", + ]); + const saveList = noApplicable + ? [] + : rowsForSave.map((row) => + buildExcretaSlurgeProcessingStatusSaveItem(row, list), + ); + + if (!noApplicable && saveList.length === 0) { + alert("해당사항 없음을 체크해주세요."); + return; + } + + response = + await statisticsInputSewageExcretaSlurgeApi.saveExcretaSlurgeProcessingStatus( + { + ...baseSaveRequest, + ...userFields, + saveList, + }, + ); + } + + if (response.status === "success") { + setDeleteListByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, []); + return next; + }); + if (tabKey === SlurgeProcessingFacStatusInpColumn.tabKey) { + preYearArticleFcltSnRef.current = new Set(); + } + setSggOptions(list); + + const param = + searchParamRef.current ?? + buildSewageExcretaSlurgeSearchParam(lastSearchedValues); + await loadActiveTabData(tabKey, param, lastSearchedValues, false, false); + alert("저장되었습니다."); + } else { + alert(response.message ?? "저장 중 오류가 발생했습니다."); + } + } catch { + alert("저장 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + })(); }; - const addArticleFunc = () => { - // 항목 추가 로직 + const isCheckedRow = (row: Record): boolean => { + const v = row.__checked; + return v === 1 || v === "1" || v === "Y" || v === "y" || v === "true"; + }; + + const getCheckedItemIndexesFromGrid = (rowCount: number): number[] => { + const gridView = getGridRef.current?.gridView as + | { + getCheckedItems?: () => number[]; + getCheckedRows?: () => number[]; + } + | undefined; + + const checked = + (typeof gridView?.getCheckedItems === "function" + ? gridView.getCheckedItems() + : typeof gridView?.getCheckedRows === "function" + ? gridView.getCheckedRows() + : []) ?? []; + + return checked.filter( + (idx) => Number.isInteger(idx) && idx >= 0 && idx < rowCount, + ); + }; + + const addArticleFunc = async () => { + const ctx = lastSearchedValues ?? searchValues; + if (!hasSearched || !ctx?.sido || ctx.sido === "ALL") { + alert("먼저 조회를 진행해주세요."); + return; + } + + const tabKey = activeTabRef.current; + if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) { + alert("항목을 추가할 수 없는 탭입니다."); + return; + } + + const sggNm = ctx.gugun && ctx.gugun !== "ALL" ? ctx.gugun : ""; + + let list = sggOptions; + if (sggOptionsSidoRef.current !== ctx.sido || list.length === 0) { + list = await loadSggOptions(ctx.sido); + sggOptionsSidoRef.current = ctx.sido; + } + if (list.length === 0) { + alert("시군구 목록을 불러오지 못했습니다."); + return; + } + + commitActiveGrid(); + const currentRows = getTabGridRows(tabKey); + const newRow = createEmptyArticleRow(tabKey, ctx.sido, sggNm); + if (ARTICLE_EDIT_TAB_KEYS.has(tabKey)) { + newRow.__isNewArticle = 1; + } + + flushSync(() => { + setSggOptions(list); + setRowDataMap((prev) => { + const next = new Map(prev); + next.set(tabKey, [...currentRows, newRow]); + return next; + }); + }); + + const statusInfo = tabStatusInfoMap.get(tabKey) ?? null; + commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, true, ctx); }; const deleteArticleFunc = () => { - // 항목 제외 로직 + const tabKey = activeTabRef.current; + if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) { + alert("항목을 제외할 수 없는 탭입니다."); + return; + } + + commitActiveGrid(); + const currentRows = getTabGridRows(tabKey); + if (currentRows.length === 0) { + alert("삭제할 데이터가 없습니다."); + return; + } + + const checkedIndexes = getCheckedItemIndexesFromGrid(currentRows.length); + const checkedIndexSet = + checkedIndexes.length > 0 + ? new Set(checkedIndexes) + : new Set( + currentRows + .map((row, idx) => (isCheckedRow(row) ? idx : -1)) + .filter((idx) => idx >= 0), + ); + + const removedRows = currentRows.filter((_, idx) => checkedIndexSet.has(idx)); + const nextRows = currentRows.filter((_, idx) => !checkedIndexSet.has(idx)); + if (removedRows.length === 0) { + alert("삭제할 항목을 선택해주세요."); + return; + } + + const persistedRemovedRows = removedRows.filter((row) => + isPersistedArticleRowForTab(tabKey, row), + ); + if (persistedRemovedRows.length > 0) { + setDeleteListByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, [...(prev.get(tabKey) ?? []), ...persistedRemovedRows]); + return next; + }); + } + + setRowDataMap((prev) => { + const next = new Map(prev); + next.set(tabKey, nextRows); + return next; + }); + + const ctx = lastSearchedValues ?? searchValues; + if (ctx) { + const statusInfo = tabStatusInfoMap.get(tabKey) ?? null; + commitToolbarSnapshotForLoadedTab( + tabKey, + statusInfo, + nextRows.length > 0, + ctx, + ); + } }; - const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); - const [isNotApplicable, setIsNotApplicable] = useState(false); + const columnInfoByTab = useMemo(() => { + const map = new Map(); + for (const tab of SewageExcretaSlurgeInpTabList) { + const tabKey = tab.tabKey; + const canEdit = + computeTabEnterable( + tabStatusInfoMap.get(tabKey) ?? null, + inputPeriodEnterable, + ) || isAdminInfo; + const rawColumns = (tab.columns ?? []) as ColumnConfig[]; + const base = canEdit + ? rawColumns + : rawColumns.map((col) => ({ ...col, editable: false as const })); + const columns = base.map((col) => { + let next: ColumnConfig = col; + if (col.fieldName === "ctpvNm") { + next = { ...col, editable: false as const, styleName: "rg-cell-readonly" }; + } else if ( + SGG_NM_DROPDOWN_TAB_KEYS.has(tabKey) && + col.fieldName === "sggNm" + ) { + next = buildSggNmColumn(col, canEdit, sggOptions); + } else if (col.fieldName === "sggNm" || col.fieldName === "swtrmtFcltNm") { + next = { ...col, editable: false as const, styleName: "rg-cell-readonly" }; + } + const guide = columnGuidesByTab[tabKey]?.[col.fieldName]?.trim(); + return guide ? { ...next, tooltip: guide } : next; + }); + map.set(tabKey, { + columns, + columnLayout: tab.columnLayout ?? [], + }); + } + return map; + }, [ + tabStatusInfoMap, + inputPeriodEnterable, + isAdminInfo, + sggOptions, + hasSearched, + lastSearchedValues, + searchValues, + columnGuidesByTab, + ]); + + const digestorOperatingCellStyleCallback = useCallback( + ({ row, fieldName, rowIndex, grid }) => { + if (fieldName === DIGESTOR_OPERATING_STATUS_EN_YN_FIELD) { + return undefined; + } + if (!isDigestorOperatingRowDisabled(row, grid, rowIndex)) { + return undefined; + } + return { styleName: "rg-cell-readonly", editable: false }; + }, + [], + ); + + const handleDigestorCellEdited = useCallback( + ({ fieldName }) => { + if (fieldName !== DIGESTOR_OPERATING_STATUS_EN_YN_FIELD) { + return; + } + // 편집 중 setRowDataMap → setRows/refresh와 충돌하지 않도록 편집 완료 후 갱신 + window.setTimeout(() => { + getGridRef.current?.gridView?.refresh?.(); + }, 0); + }, + [], + ); + + const resolveGridCellStyleCallback = useCallback( + (tabKey: string): CellStyleCallback => + (params) => { + const verification = verificationCellStyle(params); + if (tabKey !== DigestorOperatingStatusInpColumn.tabKey) { + return verification; + } + return mergeCellStyleResults( + verification, + digestorOperatingCellStyleCallback(params), + ); + }, + [digestorOperatingCellStyleCallback], + ); + + const searchContextForToolbar = lastSearchedValues ?? searchValues; + const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; + const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab); + const activeTabToolbar = computeTabToolbarSnapshot( + activeTabStatus, + activeTabHasRowData, + hasSearched, + searchContextForToolbar, + inputPeriodEnterable, + isAdminInfo, + ); + + const { + showSaveInToolbar: baseShowSaveInToolbar, + showEnterableToolbarButtons: baseShowEnterableToolbarButtons, + showActionToolbarCard: baseShowActionToolbarCard, + } = activeTabToolbar; + + const showSaveInToolbar = baseShowSaveInToolbar && toolbarGroupAllowed; + const showEnterableToolbarButtons = + baseShowEnterableToolbarButtons && toolbarGroupAllowed; + const showActionToolbarCard = + toolbarGroupAllowed && + (baseShowActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab))); + const showArticleEditButtons = + ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons; + const showNotApplicableForActiveTab = + loadedTabKeys.has(activeTab) && + shouldShowNotApplicable(activeTabHasRowData, activeTabStatus); + const showSecondaryToolbarCard = + hasSearched && + toolbarGroupAllowed && + (showSaveInToolbar || showEnterableToolbarButtons); + const isNotApplicableForActiveTab = isNotApplicableByTab.get(activeTab) ?? false; + const setIsNotApplicableForActiveTab: Dispatch> = useCallback( + (next) => { + const tabKey = activeTabRef.current; + const current = isNotApplicableByTab.get(tabKey) ?? false; + const resolved = + typeof next === "function" + ? (next as (prevState: boolean) => boolean)(current) + : next; + setIsNotApplicableForTab(tabKey, resolved); + }, + [isNotApplicableByTab, setIsNotApplicableForTab], + ); + + const openVerificationHistoryDialog = () => { + const ctx = lastSearchedValues ?? searchValues; + const activeTabStatsCd = tabStatsCdMap.get(activeTab); + if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !activeTabStatsCd) { + alert("먼저 조회를 진행해주세요."); + return; + } + setVerificationHistoryOpen(true); + }; + + const verificationQueryContext = (() => { + const ctx = lastSearchedValues ?? searchValues; + const statsCd = tabStatsCdMap.get(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 ( <> - -
-
- - - - +
+ + {showActionToolbarCard && ( +
+
+ + {showEnterableToolbarButtons && ( + <> + void lastYearDataFunc()} + /> + + + )} + {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + + )} + {showSaveInToolbar && } +
+
+ )} + {showSecondaryToolbarCard && ( +
+
+ {showNotApplicableForActiveTab && ( + + )} + {showArticleEditButtons && ( + <> + + + + )} +
+
+ )} + {isLoading && ( +
+
+ +
+
+ )} +
+ void handleActiveTabChange(v)} + className="w-full" + variant="default" + > + + {SewageExcretaSlurgeInpTabList.map((info) => ( + + {info.tabLabel || info.tabKey} + + ))} + + {SewageExcretaSlurgeInpTabList.map((info) => { + const tabKey = info.tabKey; + const rowData = rowDataMap.get(tabKey) ?? []; + const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; + + return ( + + { + setRowDataMap((prev) => { + const next = new Map(prev); + const cur = next.get(tabKey) ?? []; + const nextRows = + typeof updater === "function" + ? updater(cur) + : updater; + next.set(tabKey, nextRows); + return next; + }); + }} + showHeaderCheckbox={ARTICLE_EDIT_TAB_KEYS.has(tabKey)} + fixedColumns={info.fixedColumns} + enableSort={false} + statusInfo={tabStatusInfo} + isExcelImport={tabRowDataExists(rowDataMap, tabKey)} + isExcelExport + getGridDataRef={ + activeTab === tabKey ? getGridDataRef : undefined + } + getGridRef={ + activeTab === tabKey ? getGridRef : undefined + } + cellStyleCallback={resolveGridCellStyleCallback(tabKey)} + cellTooltipCallback={verificationCellTooltip} + onCellEdited={ + tabKey === DigestorOperatingStatusInpColumn.tabKey + ? handleDigestorCellEdited + : undefined + } + /> + + ); + })} +
-
-
- - - -
-
- { + const values = lastSearchedValues ?? searchValues; + if (!values || !searchParamRef.current) return; + await loadActiveTabData( + activeTabRef.current, + searchParamRef.current, + values, + false, + false, + ); + }} /> + { + const values = lastSearchedValues ?? searchValues; + if (!values || !searchParamRef.current) return; + await loadActiveTabData( + activeTabRef.current, + searchParamRef.current, + values, + false, + false, + ); + }} + /> + + ); }; diff --git a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx index 1c90c2a62..abd48918a 100644 --- a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx @@ -37,7 +37,11 @@ import { type SewerageServiceLevelResidentFriendlyFactSaveItem, type SewerageServiceLevelVerificationResult, } from "@/entities/statistics/model/input/statistics-input-sewerage-service-level-interface"; -import type { SewerageStatusInfo, cancelSubmitRequest, ImrStatusResponse } from "@/entities/statistics/model/statistics-interface"; +import type { + SewerageStatusInfo, + cancelSubmitRequest, + ImrStatusResponse, +} from "@/entities/statistics/model/statistics-interface"; import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; @@ -62,6 +66,18 @@ const SEWERAGE_SERVICE_LEVEL_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY: Record & { verificationResult?: SewerageServiceLevelVerificationResult; }; @@ -299,6 +315,8 @@ const SewerageServiceLevelInp = () => { const periodEnterableRef = useRef(true); const activeTabRef = useRef(activeTab); const getGridDataRef = useRef<(() => Record[]) | null>(null); + /** ST00130002 당해 조회 결과 유무(공공하수처리시설 연동) — 전년도 조회 가능 여부 */ + const publicSewageFacilitiesAvailableRef = useRef(false); activeTabRef.current = activeTab; useEffect(() => { @@ -384,9 +402,10 @@ const SewerageServiceLevelInp = () => { searchContext: SearchFormValues, mergeIntoExistingMap: boolean, updateLastSearchedValues: boolean, - overrides?: { mode?: string }, + overrides?: { mode?: string; alertIfResidentFriendlyEmpty?: boolean }, ): Promise => { const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param; + const fromPreYear = overrides?.mode === "PRE"; if (tabKey === ComplaintInpColumn.tabKey) { const response = @@ -426,6 +445,12 @@ const SewerageServiceLevelInp = () => { (residentFriendlyFactList ?? []) as SewerageServiceLevelResidentFriendlyFactItem[], commonInfo.ctpvNm, ); + if (!fromPreYear) { + publicSewageFacilitiesAvailableRef.current = gridRows.length > 0; + if (gridRows.length === 0 && overrides?.alertIfResidentFriendlyEmpty) { + alert(EMPTY_PUBLIC_SEWAGE_FACILITIES_MSG); + } + } applyRowMap(mergeIntoExistingMap, (m) => { m.set(ResidentFriendlyFactInpColumn.tabKey, gridRows); @@ -460,6 +485,7 @@ const SewerageServiceLevelInp = () => { searchParamRef.current = null; searchDoneRef.current = false; periodEnterableRef.current = true; + publicSewageFacilitiesAvailableRef.current = false; }; const handleSearch = async (values: SearchFormValues) => { @@ -507,6 +533,7 @@ const SewerageServiceLevelInp = () => { values, !yearChangedFromLastSearch, true, + buildEmptyDataAlertOverrides(activeTabRef.current), ); } catch { alert("데이터 조회 중 오류가 발생했습니다."); @@ -541,6 +568,7 @@ const SewerageServiceLevelInp = () => { lastSearchedValues, true, false, + buildEmptyDataAlertOverrides(tabKey), ); } catch { alert("데이터 조회 중 오류가 발생했습니다."); @@ -559,6 +587,13 @@ const SewerageServiceLevelInp = () => { alert("먼저 데이터를 조회해주세요."); return; } + if ( + activeTabRef.current === ResidentFriendlyFactInpColumn.tabKey && + !publicSewageFacilitiesAvailableRef.current + ) { + alert(EMPTY_PUBLIC_SEWAGE_FACILITIES_MSG); + return; + } try { setIsLoading(true); diff --git a/apps/st/src/components/statistics/input/WaterReuseInp.tsx b/apps/st/src/components/statistics/input/WaterReuseInp.tsx index 2df784f6c..a5726399a 100644 --- a/apps/st/src/components/statistics/input/WaterReuseInp.tsx +++ b/apps/st/src/components/statistics/input/WaterReuseInp.tsx @@ -481,17 +481,6 @@ function mapTreatWaterReuseStatusListToGridRows< }); } -function mapWaterReuseListToGridRows< - T extends { verificationResult?: WaterReuseVerificationResult }, ->(list: T[], ctpvNm: string): GridRowWithVerification[] { - return list.map((item) => { - const { verificationResult, ...rest } = item; - const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; - if (verificationResult) row.verificationResult = verificationResult; - return row; - }); -} - const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); async function waitForImrJobCompleted( @@ -1871,7 +1860,6 @@ const WaterReuseInp = () => { }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab, sggOptions]); const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; - const activeTabRowData = rowDataMap.get(activeTab) ?? []; const activeTabToolbar = toolbarSnapshotByTab.get(activeTab) ?? computeTabToolbarSnapshot( diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index d44b32a81..1c9fc9bae 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -184,6 +184,13 @@ export type ComputedFieldConfig = numeratorQuotient: [string, string]; denominatorQuotient: [string, string]; type: "quotientRatioPercent"; + } + | { + /** (numeratorField / denominatorField) × multiplierField × 100 */ + numeratorField: string; + denominatorField: string; + multiplierField: string; + type: "quotientTimesMultiplierPercent"; }; export type ColumnConfig = { @@ -306,15 +313,30 @@ export type ColumnInfoList = { }; // 개별 셀 스타일을 동적으로 결정하기 위한 콜백 (예: 검증 오류 셀 강조) +export type CellStyleCallbackGrid = { + getValue?: (itemIndex: number, fieldName: string) => unknown; +}; + export type CellStyleCallbackParams = { rowIndex: number; fieldName: string; row: Record; + /** styleCallback 시점의 그리드 값 조회(행 단위 동적 스타일용) */ + grid?: CellStyleCallbackGrid; }; +/** RealGrid styleCallback — 문자열(styleName) 또는 editable 등 동적 스타일 객체 */ +export type CellStyleCallbackResult = + | string + | { + styleName?: string; + editable?: boolean; + readOnly?: boolean; + }; + export type CellStyleCallback = ( params: CellStyleCallbackParams, -) => string | undefined; +) => CellStyleCallbackResult | undefined; /** 셀 hover 시 툴팁 문구 (반환 시 해당 문구 표시, 검증 오류 메시지 등) */ export type CellTooltipCallbackParams = { diff --git a/apps/st/src/components/util/column/input/sewageExcretaRelated/ExcretaRelatedIndustryInpColumn.ts b/apps/st/src/components/util/column/input/sewageExcretaRelated/ExcretaRelatedIndustryInpColumn.ts index a2b645123..e8e0991f0 100644 --- a/apps/st/src/components/util/column/input/sewageExcretaRelated/ExcretaRelatedIndustryInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageExcretaRelated/ExcretaRelatedIndustryInpColumn.ts @@ -5,74 +5,75 @@ import { ValueType } from "realgrid"; export const ExcretaRelatedIndustryInpColumn = { columns: [ // 행정구역 - { name: 'SIDO', fieldName: '시도', header: { text: '시도' }, editable: false }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } }, - - // 행정코드 (hidden) - { name: 'ADST_DSTR_CD', fieldName: '행정구역코드', header: { text: '행정구역코드' }, visible: false }, - { name: 'ADST_CD', fieldName: '행정코드', header: { text: '행정코드' }, visible: false }, - { name: 'ADST_DONG', fieldName: '행정동', header: { text: '행정동' }, visible: false }, - { name: 'RGN_NM', fieldName: '지역명', header: { text: '지역명' }, visible: false }, + { 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: 'ECSCS_SM', fieldName: '업체수(개소)', header: { text: '업체수(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'bzentyCnt', fieldName: 'bzentyCnt', header: { text: '업체수(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 종사인원(명) - { name: 'ECSCS_BSMN', fieldName: '종사인원(명)', header: { text: '종사인원(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'empNope', fieldName: 'empNope', header: { text: '종사인원(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 시설(차량) 확보현황(대수) - { name: 'VHCLE_SM', fieldName: '계', header: { text: '계' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'THREE_BELOW', fieldName: '3톤이하', header: { text: '3톤이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'FOUR_BELOW', fieldName: '4.5톤이하', header: { text: '4.5톤이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'EIGHT_BELOW', fieldName: '8톤이하', header: { text: '8톤이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'ETC', fieldName: '기타', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { + name: 'fcltSecrPrstCnt', + fieldName: 'fcltSecrPrstCnt', + header: { text: '계' }, + editable: false, footer: + footerInt, + valueType: ValueType.NUMBER, + dataFormat: '#,##0', + styleName: 'rg-cell-total', + computed: { + fields: ['n3TonUdrCnt', 'n4TonUdrCnt', 'n8TonUdrCnt', 'etcCnt'], + type: 'sum' as const, + }, + }, + { name: 'n3TonUdrCnt', fieldName: 'n3TonUdrCnt', header: { text: '3톤이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'n4TonUdrCnt', fieldName: 'n4TonUdrCnt', header: { text: '4.5톤이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'n8TonUdrCnt', fieldName: 'n8TonUdrCnt', header: { text: '8톤이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'etcCnt', fieldName: 'etcCnt', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 수집·운반 실적(m³/년) - { name: 'COLCT_TRSP_ACMSLT', fieldName: '수집·운반 실적(m³/년)', header: { text: '수집·운반 실적(m³/년)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'clctCnvncPrfmncQnt', fieldName: 'clctCnvncPrfmncQnt', header: { text: '수집·운반 실적(m³/년)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 비고 - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - // Hidden: 의견 필드 - { name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' }, visible: false }, - { name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' }, visible: false }, - - // Hidden: Check fields : 데이터 변경 여부를 판단하는 필드로, UI에서 숨김 처리 - { name: 'CHK_ECSCS_SM', fieldName: 'CHK_ECSCS_SM', header: { text: 'CHK_ECSCS_SM' }, visible: false }, - { name: 'CHK_ECSCS_BSMN', fieldName: 'CHK_ECSCS_BSMN', header: { text: 'CHK_ECSCS_BSMN' }, visible: false }, - { name: 'CHK_VHCLE_SM', fieldName: 'CHK_VHCLE_SM', header: { text: 'CHK_VHCLE_SM' }, visible: false }, - { name: 'CHK_THREE_BELOW', fieldName: 'CHK_THREE_BELOW', header: { text: 'CHK_THREE_BELOW' }, visible: false }, - { name: 'CHK_FOUR_BELOW', fieldName: 'CHK_FOUR_BELOW', header: { text: 'CHK_FOUR_BELOW' }, visible: false }, - { name: 'CHK_EIGHT_BELOW', fieldName: 'CHK_EIGHT_BELOW', header: { text: 'CHK_EIGHT_BELOW' }, visible: false }, - { name: 'CHK_ETC', fieldName: 'CHK_ETC', header: { text: 'CHK_ETC' }, visible: false }, - { name: 'CHK_COLCT_TRSP_ACMSLT', fieldName: 'CHK_COLCT_TRSP_ACMSLT', header: { text: 'CHK_COLCT_TRSP_ACMSLT' }, visible: false }, + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false }, ], columnLayout: [ { name: 'group_admin', header: { text: '행정구역' }, - items: ['SIDO', 'GUGUN'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, - 'ECSCS_SM', - 'ECSCS_BSMN', + 'bzentyCnt', + 'empNope', { name: 'group_vehicle_status', header: { text: '시설(차량) 확보현황(대수)' }, items: [ - 'VHCLE_SM', - 'THREE_BELOW', - 'FOUR_BELOW', - 'EIGHT_BELOW', - 'ETC' + 'fcltSecrPrstCnt', + 'n3TonUdrCnt', + 'n4TonUdrCnt', + 'n8TonUdrCnt', + 'etcCnt' ] }, - 'COLCT_TRSP_ACMSLT', - 'RMK' + 'clctCnvncPrfmncQnt', + 'rmrk', + 'admdstCd', + 'admdstNm', ], rowStartNum: 2, - fixedColumns: 1, - tabKey: 'excretaRelatedIndustryInp', + fixedColumns: 0, + tabKey: 'ST00120003', tabLabel: '분뇨수집·운반업' }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn.ts b/apps/st/src/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn.ts index 5e61f6afe..53fa25308 100644 --- a/apps/st/src/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn.ts @@ -5,82 +5,59 @@ import { ValueType } from "realgrid"; export const PrivateSewageTreatRelatedIndustryInpColumn = { columns: [ // 행정구역 - { name: 'SIDO', fieldName: '시도', header: { text: '시도' }, editable: false }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } }, - - // 행정코드 (hidden) - { name: 'ADST_DONG', fieldName: '행정동', header: { text: '행정동' }, visible: false }, - { name: 'RGN_NM', fieldName: '지역명', header: { text: '지역명' }, visible: false }, - { name: 'ADST_CD', fieldName: '행정코드', header: { text: '행정코드' }, visible: false }, + { 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: 'CNSTRCT_ENTRPS', fieldName: '업소수', header: { text: '업소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'CNSTRCT_BSMN', fieldName: '종사인원(명)', header: { text: '종사인원(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'designMdsrCnt', fieldName: 'designMdsrCnt', header: { text: '업소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'designMdsrNope', fieldName: 'designMdsrNope', header: { text: '종사인원(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 개인하수처리시설 제조업 - { name: 'MNFCTUR_ENTRPS', fieldName: '업소수', header: { text: '업소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'MNFCTUR_BSMN', fieldName: '종사인원(명)', header: { text: '종사인원(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'mfbizCnt', fieldName: 'mfbizCnt', header: { text: '업소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'mfbizMdsrNope', fieldName: 'mfbizMdsrNope', header: { text: '종사인원(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 개인하수처리시설 관리업 - { name: 'MGMT_ENTRPS', fieldName: '업소수', header: { text: '업소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'MGMT_BSMN', fieldName: '종사인원(명)', header: { text: '종사인원(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'mngCnt', fieldName: 'mngCnt', header: { text: '업소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'mngMdsrNope', fieldName: 'mngMdsrNope', header: { text: '종사인원(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 비고 - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - // Hidden: 의견 필드 - { name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' }, visible: false }, - { name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' }, visible: false }, - - // Hidden: 추가 필드 (화면에 미노출이, DB 저장) - { name: 'CNSTRCT_FCT_CNT', fieldName: '설계시공시설개수', header: { text: '설계시공시설개수' }, visible: false }, - { name: 'CNSTRCT_FCT_CPCTY', fieldName: '설계시공시설용량', header: { text: '설계시공시설용량' }, visible: false }, - { name: 'MNFCTUR_STTY_CNT', fieldName: '제조정원', header: { text: '제조정원' }, visible: false }, - { name: 'MNFCTUR_WRRTN_CNT', fieldName: '제조권원', header: { text: '제조권원' }, visible: false }, - { name: 'MGMT_STTY_CNT', fieldName: '관리정원', header: { text: '관리정원' }, visible: false }, - { name: 'MGMT_WRRTN_CNT', fieldName: '관리권원', header: { text: '관리권원' }, visible: false }, - - // Hidden: Check fields : 데이터 변경 여부를 판단하는 필드로, UI에서 숨김 처리 - { name: 'CHK_CNSTRCT_ENTRPS', fieldName: 'CHK_CNSTRCT_ENTRPS', header: { text: 'CHK_CNSTRCT_ENTRPS' }, visible: false }, - { name: 'CHK_CNSTRCT_BSMN', fieldName: 'CHK_CNSTRCT_BSMN', header: { text: 'CHK_CNSTRCT_BSMN' }, visible: false }, - { name: 'CHK_CNSTRCT_FCT_CNT', fieldName: 'CHK_CNSTRCT_FCT_CNT', header: { text: 'CHK_CNSTRCT_FCT_CNT' }, visible: false }, - { name: 'CHK_CNSTRCT_FCT_CPCTY', fieldName: 'CHK_CNSTRCT_FCT_CPCTY', header: { text: 'CHK_CNSTRCT_FCT_CPCTY' }, visible: false }, - { name: 'CHK_MNFCTUR_ENTRPS', fieldName: 'CHK_MNFCTUR_ENTRPS', header: { text: 'CHK_MNFCTUR_ENTRPS' }, visible: false }, - { name: 'CHK_MNFCTUR_BSMN', fieldName: 'CHK_MNFCTUR_BSMN', header: { text: 'CHK_MNFCTUR_BSMN' }, visible: false }, - { name: 'CHK_MNFCTUR_STTY_CNT', fieldName: 'CHK_MNFCTUR_STTY_CNT', header: { text: 'CHK_MNFCTUR_STTY_CNT' }, visible: false }, - { name: 'CHK_MNFCTUR_WRRTN_CNT', fieldName: 'CHK_MNFCTUR_WRRTN_CNT', header: { text: 'CHK_MNFCTUR_WRRTN_CNT' }, visible: false }, - { name: 'CHK_MGMT_ENTRPS', fieldName: 'CHK_MGMT_ENTRPS', header: { text: 'CHK_MGMT_ENTRPS' }, visible: false }, - { name: 'CHK_MGMT_BSMN', fieldName: 'CHK_MGMT_BSMN', header: { text: 'CHK_MGMT_BSMN' }, visible: false }, - { name: 'CHK_MGMT_STTY_CNT', fieldName: 'CHK_MGMT_STTY_CNT', header: { text: 'CHK_MGMT_STTY_CNT' }, visible: false }, - { name: 'CHK_MGMT_WRRTN_CNT', fieldName: 'CHK_MGMT_WRRTN_CNT', header: { text: 'CHK_MGMT_WRRTN_CNT' }, visible: false }, + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false }, ], columnLayout: [ { name: 'group_admin', header: { text: '행정구역' }, - items: ['SIDO', 'GUGUN'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, { name: 'group_design_construct', header: { text: '개인하수처리시설 설계·시공업' }, - items: ['CNSTRCT_ENTRPS', 'CNSTRCT_BSMN'] + items: ['designMdsrCnt', 'designMdsrNope'] }, { name: 'group_manufacture', header: { text: '개인하수처리시설 제조업' }, - items: ['MNFCTUR_ENTRPS', 'MNFCTUR_BSMN'] + items: ['mfbizCnt', 'mfbizMdsrNope'] }, { name: 'group_management', header: { text: '개인하수처리시설 관리업' }, - items: ['MGMT_ENTRPS', 'MGMT_BSMN'] + items: ['mngCnt', 'mngMdsrNope'] }, - 'RMK' + 'rmrk', + 'admdstCd', + 'admdstNm', ], rowStartNum: 2, - fixedColumns: 1, - tabKey: 'privateSewageTreatRelatedIndustryInp', + fixedColumns: 0, + tabKey: 'ST00120004', tabLabel: '개인하수처리관련업' }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn.ts b/apps/st/src/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn.ts index 8df679707..7df3d801c 100644 --- a/apps/st/src/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn.ts @@ -5,86 +5,107 @@ import { ValueType } from "realgrid"; export const PublicInstitutionExcretaIndustryInpColumn = { columns: [ // 행정구역 - { name: 'SIDO', fieldName: '시도', header: { text: '시도' }, editable: false }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } }, - - // 행정코드 (hidden) - { name: 'ADST_CD', fieldName: '행정코드', header: { text: '행정코드' }, visible: false }, + { 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: 'OCGNZ', fieldName: '직 제', header: { text: '직 제' } }, + { name: 'ognzNm', fieldName: 'ognzNm', header: { text: '직 제' } }, // 직원총수 - { name: 'TOT_WTR_QRM', fieldName: '직원총수', header: { text: '직원총수' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { + name: 'empGrssCnt', + fieldName: 'empGrssCnt', + header: { text: '직원총수' }, + editable: false, + footer: footerInt, + valueType: ValueType.NUMBER, + dataFormat: '#,##0', + styleName: 'rg-cell-total', + computed: { + fields: ['engrkCnt', 'mchnCnt', 'prvtmCnt', 'chmengCnt', 'envCnt', 'enjbEtcCnt', 'etcCnt'], + type: 'sum' as const, + }, + }, // 행정직 - { name: 'ADMINIST_JSSFC', fieldName: '행정직', header: { text: '행정직' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'pbadmsNope', fieldName: 'pbadmsNope', header: { text: '행정직' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 기술직 - { name: 'TCHNLGY_JSSFC_SM', fieldName: '계', header: { text: '계' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'ENGRK_JSSFC', fieldName: '토목', header: { text: '토목' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'MCHN_JSSFC', fieldName: '기계', header: { text: '기계' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'ELCTY_JSSFC', fieldName: '전기', header: { text: '전기' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'CHMSTRY_JSSFC', fieldName: '화공', header: { text: '화공' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'ENVRN_JSSFC', fieldName: '환경', header: { text: '환경' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'TCHNLGY_ETC', fieldName: '기타', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - - // 기능직 (hidden) - { name: 'TCNSRV', fieldName: '기능직', header: { text: '기능직' }, visible: false }, + { + name: 'techWholNope', + fieldName: 'techWholNope', + header: { text: '계' }, + editable: false, + footer: footerInt, + valueType: ValueType.NUMBER, + dataFormat: '#,##0', + styleName: 'rg-cell-total', + computed: { + fields: ['engrkCnt', 'mchnCnt', 'prvtmCnt', 'chmengCnt', 'envCnt', 'enjbEtcCnt'], + type: 'sum' as const, + }, + }, + { name: 'engrkCnt', fieldName: 'engrkCnt', header: { text: '토목' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'mchnCnt', fieldName: 'mchnCnt', header: { text: '기계' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'prvtmCnt', fieldName: 'prvtmCnt', header: { text: '전기' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'chmengCnt', fieldName: 'chmengCnt', header: { text: '화공' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'envCnt', fieldName: 'envCnt', header: { text: '환경' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'enjbEtcCnt', fieldName: 'enjbEtcCnt', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 기타 - { name: 'ETC', fieldName: '기타', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'etcCnt', fieldName: 'etcCnt', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 비고 - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - // Hidden: 의견 필드 - { name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' }, visible: false }, - { name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' }, visible: false }, - - // Hidden: Check fields : 데이터 변경 여부를 판단하는 필드로, UI에서 숨김 처리 - { name: 'CHK_OCGNZ', fieldName: 'CHK_OCGNZ', header: { text: 'CHK_OCGNZ' }, visible: false }, - { name: 'CHK_TOT_WTR_QRM', fieldName: 'CHK_TOT_WTR_QRM', header: { text: 'CHK_TOT_WTR_QRM' }, visible: false }, - { name: 'CHK_ADMINIST_JSSFC', fieldName: 'CHK_ADMINIST_JSSFC', header: { text: 'CHK_ADMINIST_JSSFC' }, visible: false }, - { name: 'CHK_TCHNLGY_JSSFC_SM', fieldName: 'CHK_TCHNLGY_JSSFC_SM', header: { text: 'CHK_TCHNLGY_JSSFC_SM' }, visible: false }, - { name: 'CHK_ENGRK_JSSFC', fieldName: 'CHK_ENGRK_JSSFC', header: { text: 'CHK_ENGRK_JSSFC' }, visible: false }, - { name: 'CHK_MCHN_JSSFC', fieldName: 'CHK_MCHN_JSSFC', header: { text: 'CHK_MCHN_JSSFC' }, visible: false }, - { name: 'CHK_ELCTY_JSSFC', fieldName: 'CHK_ELCTY_JSSFC', header: { text: 'CHK_ELCTY_JSSFC' }, visible: false }, - { name: 'CHK_CHMSTRY_JSSFC', fieldName: 'CHK_CHMSTRY_JSSFC', header: { text: 'CHK_CHMSTRY_JSSFC' }, visible: false }, - { name: 'CHK_ENVRN_JSSFC', fieldName: 'CHK_ENVRN_JSSFC', header: { text: 'CHK_ENVRN_JSSFC' }, visible: false }, - { name: 'CHK_TCHNLGY_ETC', fieldName: 'CHK_TCHNLGY_ETC', header: { text: 'CHK_TCHNLGY_ETC' }, visible: false }, - { name: 'CHK_TCNSRV', fieldName: 'CHK_TCNSRV', header: { text: 'CHK_TCNSRV' }, visible: false }, - { name: 'CHK_ETC', fieldName: 'CHK_ETC', header: { text: 'CHK_ETC' }, visible: false }, + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false }, + { name: 'ognzSn', fieldName: 'ognzSn', header: { text: '조직일련번호' }, visible: false }, + { + name: '__isNewArticle', + fieldName: '__isNewArticle', + header: { text: '신규항목' }, + visible: false, + editable: false, + valueType: ValueType.NUMBER, + }, ], columnLayout: [ { name: 'group_admin', header: { text: '행정구역' }, - items: ['SIDO', 'GUGUN'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, - 'OCGNZ', - 'TOT_WTR_QRM', - 'ADMINIST_JSSFC', + 'ognzNm', + 'empGrssCnt', + 'pbadmsNope', { name: 'group_technology', header: { text: '기술직' }, items: [ - 'TCHNLGY_JSSFC_SM', - 'ENGRK_JSSFC', - 'MCHN_JSSFC', - 'ELCTY_JSSFC', - 'CHMSTRY_JSSFC', - 'ENVRN_JSSFC', - 'TCHNLGY_ETC' + 'techWholNope', + 'engrkCnt', + 'mchnCnt', + 'prvtmCnt', + 'chmengCnt', + 'envCnt', + 'enjbEtcCnt' ] }, - 'ETC', - 'RMK' + 'etcCnt', + 'rmrk', + 'admdstCd', + 'admdstNm', + 'ognzSn', + '__isNewArticle', ], rowStartNum: 2, fixedColumns: 0, - tabKey: 'publicInstitutionExcretaIndustryInp', + tabKey: 'ST00120002', tabLabel: '공공기관 분뇨분야' }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn.ts b/apps/st/src/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn.ts index 9df115260..176c11a1a 100644 --- a/apps/st/src/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn.ts @@ -5,86 +5,107 @@ import { ValueType } from "realgrid"; export const PublicInstitutionSewerIndustryInpColumn = { columns: [ // 행정구역 - { name: 'SIDO', fieldName: '시도', header: { text: '시도' }, editable: false }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } }, - - // 행정코드 (hidden) - { name: 'ADST_CD', fieldName: '행정코드', header: { text: '행정코드' }, visible: false }, + { 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: 'OCGNZ', fieldName: '직 제', header: { text: '직 제' } }, + { name: 'ognzNm', fieldName: 'ognzNm', header: { text: '직 제' } }, // 직원총수 - { name: 'TOT_WTR_QRM', fieldName: '직원총수', header: { text: '직원총수' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { + name: 'empGrssCnt', + fieldName: 'empGrssCnt', + header: { text: '직원총수' }, + editable: false, + footer: footerInt, + valueType: ValueType.NUMBER, + dataFormat: '#,##0', + styleName: 'rg-cell-total', + computed: { + fields: ['engrkCnt', 'mchnCnt', 'prvtmCnt', 'chmengCnt', 'envCnt', 'enjbEtcCnt', 'etcCnt'], + type: 'sum' as const, + }, + }, // 행정직 - { name: 'ADMINIST_JSSFC', fieldName: '행정직', header: { text: '행정직' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'pbadmsNope', fieldName: 'pbadmsNope', header: { text: '행정직' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 기술직 - { name: 'TCHNLGY_JSSFC_SM', fieldName: '계', header: { text: '계' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'ENGRK_JSSFC', fieldName: '토목', header: { text: '토목' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'MCHN_JSSFC', fieldName: '기계', header: { text: '기계' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'ELCTY_JSSFC', fieldName: '전기', header: { text: '전기' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'CHMSTRY_JSSFC', fieldName: '화공', header: { text: '화공' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'ENVRN_JSSFC', fieldName: '환경', header: { text: '환경' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'TCHNLGY_ETC', fieldName: '기타', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - - // 기능직 (hidden) - { name: 'TCNSRV', fieldName: '기능직', header: { text: '기능직' }, visible: false }, + { + name: 'techWholNope', + fieldName: 'techWholNope', + header: { text: '계' }, + editable: false, + footer: footerInt, + valueType: ValueType.NUMBER, + dataFormat: '#,##0', + styleName: 'rg-cell-total', + computed: { + fields: ['engrkCnt', 'mchnCnt', 'prvtmCnt', 'chmengCnt', 'envCnt', 'enjbEtcCnt'], + type: 'sum' as const, + }, + }, + { name: 'engrkCnt', fieldName: 'engrkCnt', header: { text: '토목' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'mchnCnt', fieldName: 'mchnCnt', header: { text: '기계' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'prvtmCnt', fieldName: 'prvtmCnt', header: { text: '전기' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'chmengCnt', fieldName: 'chmengCnt', header: { text: '화공' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'envCnt', fieldName: 'envCnt', header: { text: '환경' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'enjbEtcCnt', fieldName: 'enjbEtcCnt', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 기타 - { name: 'ETC', fieldName: '기타', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'etcCnt', fieldName: 'etcCnt', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 비고 - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - // Hidden: 의견 필드 - { name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' }, visible: false }, - { name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' }, visible: false }, - - // Hidden: Check fields : 데이터 변경 여부를 판단하는 필드로, UI에서 숨김 처리 - { name: 'CHK_OCGNZ', fieldName: 'CHK_OCGNZ', header: { text: 'CHK_OCGNZ' }, visible: false }, - { name: 'CHK_TOT_WTR_QRM', fieldName: 'CHK_TOT_WTR_QRM', header: { text: 'CHK_TOT_WTR_QRM' }, visible: false }, - { name: 'CHK_ADMINIST_JSSFC', fieldName: 'CHK_ADMINIST_JSSFC', header: { text: 'CHK_ADMINIST_JSSFC' }, visible: false }, - { name: 'CHK_TCHNLGY_JSSFC_SM', fieldName: 'CHK_TCHNLGY_JSSFC_SM', header: { text: 'CHK_TCHNLGY_JSSFC_SM' }, visible: false }, - { name: 'CHK_ENGRK_JSSFC', fieldName: 'CHK_ENGRK_JSSFC', header: { text: 'CHK_ENGRK_JSSFC' }, visible: false }, - { name: 'CHK_MCHN_JSSFC', fieldName: 'CHK_MCHN_JSSFC', header: { text: 'CHK_MCHN_JSSFC' }, visible: false }, - { name: 'CHK_ELCTY_JSSFC', fieldName: 'CHK_ELCTY_JSSFC', header: { text: 'CHK_ELCTY_JSSFC' }, visible: false }, - { name: 'CHK_CHMSTRY_JSSFC', fieldName: 'CHK_CHMSTRY_JSSFC', header: { text: 'CHK_CHMSTRY_JSSFC' }, visible: false }, - { name: 'CHK_ENVRN_JSSFC', fieldName: 'CHK_ENVRN_JSSFC', header: { text: 'CHK_ENVRN_JSSFC' }, visible: false }, - { name: 'CHK_TCHNLGY_ETC', fieldName: 'CHK_TCHNLGY_ETC', header: { text: 'CHK_TCHNLGY_ETC' }, visible: false }, - { name: 'CHK_TCNSRV', fieldName: 'CHK_TCNSRV', header: { text: 'CHK_TCNSRV' }, visible: false }, - { name: 'CHK_ETC', fieldName: 'CHK_ETC', header: { text: 'CHK_ETC' }, visible: false }, + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false }, + { name: 'ognzSn', fieldName: 'ognzSn', header: { text: '조직일련번호' }, visible: false }, + { + name: '__isNewArticle', + fieldName: '__isNewArticle', + header: { text: '신규항목' }, + visible: false, + editable: false, + valueType: ValueType.NUMBER, + }, ], columnLayout: [ { name: 'group_admin', header: { text: '행정구역' }, - items: ['SIDO', 'GUGUN'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, - 'OCGNZ', - 'TOT_WTR_QRM', - 'ADMINIST_JSSFC', + 'ognzNm', + 'empGrssCnt', + 'pbadmsNope', { name: 'group_technology', header: { text: '기술직' }, items: [ - 'TCHNLGY_JSSFC_SM', - 'ENGRK_JSSFC', - 'MCHN_JSSFC', - 'ELCTY_JSSFC', - 'CHMSTRY_JSSFC', - 'ENVRN_JSSFC', - 'TCHNLGY_ETC' + 'techWholNope', + 'engrkCnt', + 'mchnCnt', + 'prvtmCnt', + 'chmengCnt', + 'envCnt', + 'enjbEtcCnt' ] }, - 'ETC', - 'RMK' + 'etcCnt', + 'rmrk', + 'admdstCd', + 'admdstNm', + 'ognzSn', + '__isNewArticle', ], rowStartNum: 2, - fixedColumns: 1, - tabKey: 'publicInstitutionSewerIndustryInp', + fixedColumns: 0, + tabKey: 'ST00120001', tabLabel: '공공기관 하수분야' }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn.ts b/apps/st/src/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn.ts index c6b70ea6c..247fdd5aa 100644 --- a/apps/st/src/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn.ts @@ -1,91 +1,158 @@ +import type { ColumnConfig } from "@/components/type/DataGrid.type"; import { footerDecimal1 } from "@/components/util/grid/footerSummary"; import { ValueType } from "realgrid"; +export const DIGESTOR_OPERATING_STATUS_YN_VALUES = ["Y", "N"] as const; +export const DIGESTOR_OPERATING_STATUS_EN_YN_FIELD = "digstnEnYn" as const; + +/** 소화조 유/무가 N이면 해당 행의 입력 셀을 잠근다(digstnEnYn 제외). */ +export function isDigestorOperatingRowDisabled( + row: Record, + grid?: { getValue?: (itemIndex: number, fieldName: string) => unknown }, + rowIndex?: number, +): boolean { + const live = + grid?.getValue != null && rowIndex != null && rowIndex >= 0 + ? grid.getValue(rowIndex, DIGESTOR_OPERATING_STATUS_EN_YN_FIELD) + : undefined; + const raw = + live !== undefined && live !== null + ? live + : row[DIGESTOR_OPERATING_STATUS_EN_YN_FIELD]; + return String(raw ?? "").trim().toUpperCase() === "N"; +} + +function dropdownColumn( + col: Pick & + Partial, + options: readonly string[], +): ColumnConfig { + const values = [...options]; + return { + ...col, + values, + labels: values, + lookupDisplay: true, + editor: { + type: "dropdown", + domainOnly: true, + textReadOnly: true, + commitOnSelect: true, + dropDownWhenClick: true, + values, + labels: values, + }, + }; +} + // 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리 > tab 2. 소화조 운영현황 export const DigestorOperatingStatusInpColumn = { columns: [ // 행정구역 - { name: 'SIDO_CD', fieldName: '시도', header: { text: '시도' }, editable: false }, - { name: 'ADST_CD', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } }, + { 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: 'BSC_FCT_NM', fieldName: '하수처리장명', header: { text: '하수처리장명' }, editable: false }, + { name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false }, // 소화조 운영현황 - { name: 'DGSTR_FNNC', fieldName: '소화조 유/무(Y/N)선택', header: { text: '소화조 유/무(Y/N)선택' } }, - { name: 'DGSTR_CPC', fieldName: '설계용량(m³)', header: { text: '설계용량(m³)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'DGSTR_OPR_CPC', fieldName: '실제운전용량(m³)', header: { text: '실제운전용량(m³)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'DGSTR_IN', fieldName: '투입량(m³/일)', header: { text: '투입량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'DGSTR_TS', fieldName: '투입 TS 농도(%)', header: { text: '투입 TS 농도(%)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'DGSTR_YRSTP_RN_DYN', fieldName: '가동일수(연간)', header: { text: '가동일수(연간)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'DGSTR_OCCUR_GAS', fieldName: '발생가스량(m³/일)', header: { text: '발생가스량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + dropdownColumn( + { + name: 'digstnEnYn', + fieldName: 'digstnEnYn', + header: { text: '소화조 유/무(Y/N)선택' }, + }, + DIGESTOR_OPERATING_STATUS_YN_VALUES, + ), + { name: 'digstnCpct', fieldName: 'digstnCpct', header: { text: '설계용량(m³)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'actlDrvnCpct', fieldName: 'actlDrvnCpct', header: { text: '실제운전용량(m³)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'digstnGasInputQnt', fieldName: 'digstnGasInputQnt', header: { text: '투입량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'digstnGasInputSldDnst', fieldName: 'digstnGasInputSldDnst', header: { text: '투입 TS 농도(%)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'digstnTnkOprtngDayCnt', fieldName: 'digstnTnkOprtngDayCnt', header: { text: '가동일수(연간)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'ocrnGasQnt', fieldName: 'ocrnGasQnt', header: { text: '발생가스량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 소화조 운영현황 > 소화가스 이용현황(m³/일) - { name: 'DGSTR_SM', fieldName: '계', header: { text: '계' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'DGSTR_SLE', fieldName: '판매', header: { text: '판매' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'DGSTR_ELTGNR', fieldName: '발전', header: { text: '발전' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'DGSTR_ONSLF', fieldName: '자체활용', header: { text: '자체활용' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'DGSTR_ETC', fieldName: '기타(CNG등)', header: { text: '기타(CNG등)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'digstnGasUtztnWholPrst', + fieldName: 'digstnGasUtztnWholPrst', + header: { text: '계' }, + footer: footerDecimal1, + editable: false, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'digstnGasUtztnNtslQnt', + 'digstnGasUtztnEtgtQnt', + 'digstnGasUtztnItslfQnt', + 'digstnGasUtztnEtcQnt', + ], + type: 'sum' as const, + }, + }, + { name: 'digstnGasUtztnNtslQnt', fieldName: 'digstnGasUtztnNtslQnt', header: { text: '판매' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'digstnGasUtztnEtgtQnt', fieldName: 'digstnGasUtztnEtgtQnt', header: { text: '발전' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'digstnGasUtztnItslfQnt', fieldName: 'digstnGasUtztnItslfQnt', header: { text: '자체활용' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'digstnGasUtztnEtcQnt', fieldName: 'digstnGasUtztnEtcQnt', header: { text: '기타(CNG등)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 소화조 운영현황 > 잉여가스량 - { name: 'DGSTR_GAS', fieldName: '잉여가스량(m³/일)', header: { text: '잉여가스량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'surplsGasQnt', fieldName: 'surplsGasQnt', header: { text: '잉여가스량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 비고 - { name: 'DGSTR_RMK', fieldName: '비고', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - // Hidden: Check fields for change detection - { name: 'CHK_DGSTR_FNNC', fieldName: 'CHK_DGSTR_FNNC', header: { text: 'CHK_DGSTR_FNNC' }, visible: false }, - { name: 'CHK_DGSTR_IN', fieldName: 'CHK_DGSTR_IN', header: { text: 'CHK_DGSTR_IN' }, visible: false }, - { name: 'CHK_DGSTR_TS', fieldName: 'CHK_DGSTR_TS', header: { text: 'CHK_DGSTR_TS' }, visible: false }, - { name: 'CHK_DGSTR_YRSTP_RN_DYN', fieldName: 'CHK_DGSTR_YRSTP_RN_DYN', header: { text: 'CHK_DGSTR_YRSTP_RN_DYN' }, visible: false }, - { name: 'CHK_DGSTR_OCCUR_GAS', fieldName: 'CHK_DGSTR_OCCUR_GAS', header: { text: 'CHK_DGSTR_OCCUR_GAS' }, visible: false }, - { name: 'CHK_DGSTR_CPC', fieldName: 'CHK_DGSTR_CPC', header: { text: 'CHK_DGSTR_CPC' }, visible: false }, - { name: 'CHK_DGSTR_OPR_CPC', fieldName: 'CHK_DGSTR_OPR_CPC', header: { text: 'CHK_DGSTR_OPR_CPC' }, visible: false }, - { name: 'CHK_DGSTR_SM', fieldName: 'CHK_DGSTR_SM', header: { text: 'CHK_DGSTR_SM' }, visible: false }, - { name: 'CHK_DGSTR_SLE', fieldName: 'CHK_DGSTR_SLE', header: { text: 'CHK_DGSTR_SLE' }, visible: false }, - { name: 'CHK_DGSTR_ELTGNR', fieldName: 'CHK_DGSTR_ELTGNR', header: { text: 'CHK_DGSTR_ELTGNR' }, visible: false }, - { name: 'CHK_DGSTR_ONSLF', fieldName: 'CHK_DGSTR_ONSLF', header: { text: 'CHK_DGSTR_ONSLF' }, visible: false }, - { name: 'CHK_DGSTR_ETC', fieldName: 'CHK_DGSTR_ETC', header: { text: 'CHK_DGSTR_ETC' }, visible: false }, - { name: 'CHK_DGSTR_GAS', fieldName: 'CHK_DGSTR_GAS', header: { text: 'CHK_DGSTR_GAS' }, visible: false }, + // hidden + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false }, + { name: 'fcltSn', fieldName: 'fcltSn', header: { text: '시설물일련번호' }, visible: false }, + { name: 'swtrmtFcltCd', fieldName: 'swtrmtFcltCd', header: { text: '처리장코드' }, visible: false }, ], columnLayout: [ { name: 'group_admin', header: { text: '행정구역' }, - items: ['SIDO_CD', 'ADST_CD'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, - 'BSC_FCT_NM', + 'swtrmtFcltNm', { name: 'group_digestor_operating', header: { text: '소화조 운영현황' }, items: [ - 'DGSTR_FNNC', - 'DGSTR_CPC', - 'DGSTR_OPR_CPC', - 'DGSTR_IN', - 'DGSTR_TS', - 'DGSTR_YRSTP_RN_DYN', - 'DGSTR_OCCUR_GAS', + 'digstnEnYn', + 'digstnCpct', + 'actlDrvnCpct', + 'digstnGasInputQnt', + 'digstnGasInputSldDnst', + 'digstnTnkOprtngDayCnt', + 'ocrnGasQnt', { name: 'group_gas_usage', header: { text: '소화가스 이용현황(m³/일)' }, items: [ - 'DGSTR_SM', - 'DGSTR_SLE', - 'DGSTR_ELTGNR', - 'DGSTR_ONSLF', - 'DGSTR_ETC' + 'digstnGasUtztnWholPrst', + 'digstnGasUtztnNtslQnt', + 'digstnGasUtztnEtgtQnt', + 'digstnGasUtztnItslfQnt', + 'digstnGasUtztnEtcQnt' ] }, - 'DGSTR_GAS' + 'surplsGasQnt' ] }, - 'DGSTR_RMK' + 'rmrk', + 'admdstCd', + 'admdstNm', + 'fcltSn', + 'swtrmtFcltCd' ], rowStartNum: 3, fixedColumns: 0, - tabKey: 'digestorOperatingStatusInp', + tabKey: 'ST00110002', tabLabel: '소화조 운영현황' }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/sewageExcretaSlurge/ExcretaSlurgeProcessingStatusInpColumn.ts b/apps/st/src/components/util/column/input/sewageExcretaSlurge/ExcretaSlurgeProcessingStatusInpColumn.ts index 7edfb7a40..60f9b14d0 100644 --- a/apps/st/src/components/util/column/input/sewageExcretaSlurge/ExcretaSlurgeProcessingStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageExcretaSlurge/ExcretaSlurgeProcessingStatusInpColumn.ts @@ -5,79 +5,116 @@ import { ValueType } from "realgrid"; export const ExcretaSlurgeProcessingStatusInpColumn = { columns: [ // 행정구역 - { name: 'SIDO_CD', fieldName: '시도', header: { text: '시도' }, editable: false }, - { name: 'ADST_CD', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } }, + { 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: 'ECSCS_NM', fieldName: '분 뇨 처리장명', header: { text: '분 뇨 처리장명' }, editable: false }, + { name: 'fcltNm', fieldName: 'fcltNm', header: { text: '분뇨 처리장명' }, editable: false }, // 발생량 - { name: 'OCTY', fieldName: '발생량 (A)=(B)+(C)', header: { text: '발생량 (A)=(B)+(C)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'gnam', + fieldName: 'gnam', + header: { text: '발생량 (A)=(B)+(C)' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'rcycQnt', + 'olandLfvlm', + 'incnrQnt', + 'oceanInputQnt', + 'fuelQnt', + 'etcQnt', + 'nondspsQnt' + ], + type: 'sum' as const, + }, + }, // 처 분 량(톤/년) - { name: 'PROCESS_CPCTY_SM', fieldName: '계(B)', header: { text: '계(B)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'RUSE', fieldName: '재활용', header: { text: '재활용' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'LAND_RCLM', fieldName: '육상매립', header: { text: '육상매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'INCNR', fieldName: '소각', header: { text: '소각' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'OCEAN_DPSL', fieldName: '해양투기', header: { text: '해양투기' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'FUEL', fieldName: '연료화', header: { text: '연료화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'ETC', fieldName: '기타', header: { text: '기타' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'wholDspsQnt', + fieldName: 'wholDspsQnt', + header: { text: '계(B)' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'rcycQnt', + 'olandLfvlm', + 'incnrQnt', + 'oceanInputQnt', + 'fuelQnt', + 'etcQnt', + ], + type: 'sum' as const, + }, + }, + { name: 'rcycQnt', fieldName: 'rcycQnt', header: { text: '재활용' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'olandLfvlm', fieldName: 'olandLfvlm', header: { text: '육상매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'incnrQnt', fieldName: 'incnrQnt', header: { text: '소각' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'oceanInputQnt', fieldName: 'oceanInputQnt', header: { text: '해양투기' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'fuelQnt', fieldName: 'fuelQnt', header: { text: '연료화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'etcQnt', fieldName: 'etcQnt', header: { text: '기타' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 미처분량(이월량)(C) - { name: 'CYFD_CPCTY', fieldName: '미처분량(이월량)(C)', header: { text: '미처분량(이월량)(C)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'nondspsQnt', fieldName: 'nondspsQnt', header: { text: '미처분량(이월량)(C)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 타시설연계여부 - { name: 'OUT_BSC_FCT_NM', fieldName: '타시설연계여부', header: { text: '타시설연계여부' } }, + { name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '타시설연계여부' } }, // 비고 - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - // Hidden: 의견 - { name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' }, visible: false }, - { name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' }, visible: false }, - - // Hidden: Check fields for change detection - { name: 'CHK_ECSCS_NM', fieldName: 'CHK_ECSCS_NM', header: { text: 'CHK_ECSCS_NM' }, visible: false }, - { name: 'CHK_OCTY', fieldName: 'CHK_OCTY', header: { text: 'CHK_OCTY' }, visible: false }, - { name: 'CHK_PROCESS_CPCTY_SM', fieldName: 'CHK_PROCESS_CPCTY_SM', header: { text: 'CHK_PROCESS_CPCTY_SM' }, visible: false }, - { name: 'CHK_RUSE', fieldName: 'CHK_RUSE', header: { text: 'CHK_RUSE' }, visible: false }, - { name: 'CHK_LAND_RCLM', fieldName: 'CHK_LAND_RCLM', header: { text: 'CHK_LAND_RCLM' }, visible: false }, - { name: 'CHK_INCNR', fieldName: 'CHK_INCNR', header: { text: 'CHK_INCNR' }, visible: false }, - { name: 'CHK_OCEAN_DPSL', fieldName: 'CHK_OCEAN_DPSL', header: { text: 'CHK_OCEAN_DPSL' }, visible: false }, - { name: 'CHK_FUEL', fieldName: 'CHK_FUEL', header: { text: 'CHK_FUEL' }, visible: false }, - { name: 'CHK_ETC', fieldName: 'CHK_ETC', header: { text: 'CHK_ETC' }, visible: false }, - { name: 'CHK_CYFD_CPCTY', fieldName: 'CHK_CYFD_CPCTY', header: { text: 'CHK_CYFD_CPCTY' }, visible: false }, - { name: 'CHK_OUT_BSC_FCT_NM', fieldName: 'CHK_OUT_BSC_FCT_NM', header: { text: 'CHK_OUT_BSC_FCT_NM' }, visible: false }, + // hidden + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false }, + { name: 'fcltSn', fieldName: 'fcltSn', header: { text: '시설물일련번호' }, visible: false }, + { name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '시설물위치내용' }, visible: false }, ], columnLayout: [ { name: 'group_admin', header: { text: '행정구역' }, - items: ['SIDO_CD', 'ADST_CD'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, - 'ECSCS_NM', - 'OCTY', + 'fcltNm', + 'gnam', { name: 'group_process_amount', header: { text: '처 분 량(톤/년)' }, items: [ - 'PROCESS_CPCTY_SM', - 'RUSE', - 'LAND_RCLM', - 'INCNR', - 'OCEAN_DPSL', - 'FUEL', - 'ETC' + 'wholDspsQnt', + 'rcycQnt', + 'olandLfvlm', + 'incnrQnt', + 'oceanInputQnt', + 'fuelQnt', + 'etcQnt' ] }, - 'CYFD_CPCTY', - 'OUT_BSC_FCT_NM', - 'RMK' + 'nondspsQnt', + 'swtrmtFcltNm', + 'rmrk', + 'admdstCd', + 'admdstNm', + 'fcltSn', + 'fcltPstnCn' ], rowStartNum: 2, fixedColumns: 2, - tabKey: 'excretaSlurgeProcessingStatusInp', + tabKey: 'ST00110004', tabLabel: '분뇨찌꺼기(슬러지)처리현황' }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn.ts b/apps/st/src/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn.ts index dedf4af98..2c779ccd3 100644 --- a/apps/st/src/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn.ts @@ -5,22 +5,37 @@ import { ValueType } from "realgrid"; export const SewageSlurgeProcessingFacStatusInpColumn = { columns: [ // 행정구역 - { name: 'SIDO_CD', fieldName: '시도', header: { text: '시도' }, editable: false }, - { name: 'ADST_CD', fieldName: '시군구', header: { text: '시군구' }, footer: { text: '합계' } }, + { name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' }, + { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-readonly' }, // 찌꺼기처리시설명 - { name: 'SLG_FCT_NM', fieldName: '찌꺼기처리시설명', header: { text: '찌꺼기처리시설명' } }, + { name: 'fcltNm', fieldName: 'fcltNm', header: { text: '찌꺼기처리시설명' } }, // 처리시설 운영현황 - { name: 'TRT_METHOD', fieldName: '처리방식', header: { text: '처리방식' } }, - { name: 'SLG_FCT_CPC', fieldName: '시설용량(톤/일)', header: { text: '시설용량(톤/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'OPR_CPC', fieldName: '연간처리량(톤/년)', header: { text: '연간처리량(톤/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'ODY_RN_TIME', fieldName: '일평균가동시간(1~24)', header: { text: '일평균가동시간(1~24)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'YRSTP_RN_DYN', fieldName: '연중 가동일(1~330)', header: { text: '연중 가동일(1~330)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'OPR_RT', fieldName: '가동율(%)', header: { text: '가동율(%)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'prcsMthExpln', fieldName: 'prcsMthExpln', header: { text: '처리방식' } }, + { name: 'fcltCpct', fieldName: 'fcltCpct', header: { text: '시설용량(톤/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'anlPrcsQnt', fieldName: 'anlPrcsQnt', header: { text: '연간처리량(톤/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'dayAvgOprtngHr', fieldName: 'dayAvgOprtngHr', header: { text: '일평균가동시간(1~24)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'anlOprtngDayCnt', fieldName: 'anlOprtngDayCnt', header: { text: '연중 가동일(1~330)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { + name: 'oprtngRt', + fieldName: 'oprtngRt', + header: { text: '가동율(%)' }, + footer: footerDecimal1, + editable: false, + styleName: 'rg-cell-total', + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + computed: { + numeratorField: 'anlPrcsQnt', + denominatorField: 'fcltCpct', + multiplierField: 'anlOprtngDayCnt', + type: 'quotientTimesMultiplierPercent' as const, + }, + }, // 비고 - { name: 'SLG_FCT_RMK', fieldName: '비고', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, // Hidden: 기본 정보 { name: 'STPN_SEQ', fieldName: 'STPN_SEQ', header: { text: 'STPN_SEQ' }, visible: false }, @@ -30,40 +45,42 @@ export const SewageSlurgeProcessingFacStatusInpColumn = { { name: 'BSNM_CD', fieldName: 'BSNM_CD', header: { text: 'BSNM_CD' }, visible: false }, { name: 'BSC_FCT_CD', fieldName: 'BSC_FCT_CD', header: { text: 'BSC_FCT_CD' }, visible: false }, - // Hidden: Check fields for change detection - { name: 'CHK_SLG_FCT_NM', fieldName: 'CHK_SLG_FCT_NM', header: { text: 'CHK_SLG_FCT_NM' }, visible: false }, - { name: 'CHK_TRT_METHOD', fieldName: 'CHK_TRT_METHOD', header: { text: 'CHK_TRT_METHOD' }, visible: false }, - { name: 'CHK_SLG_FCT_CPC', fieldName: 'CHK_SLG_FCT_CPC', header: { text: 'CHK_SLG_FCT_CPC' }, visible: false }, - { name: 'CHK_OPR_CPC', fieldName: 'CHK_OPR_CPC', header: { text: 'CHK_OPR_CPC' }, visible: false }, - { name: 'CHK_ODY_RN_TIME', fieldName: 'CHK_ODY_RN_TIME', header: { text: 'CHK_ODY_RN_TIME' }, visible: false }, - { name: 'CHK_YRSTP_RN_DYN', fieldName: 'CHK_YRSTP_RN_DYN', header: { text: 'CHK_YRSTP_RN_DYN' }, visible: false }, - { name: 'CHK_OPR_RT', fieldName: 'CHK_OPR_RT', header: { text: 'CHK_OPR_RT' }, visible: false }, + // hidden + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false }, + { name: 'fcltSn', fieldName: 'fcltSn', header: { text: '시설물일련번호' }, visible: false }, ], columnLayout: [ { name: 'group_admin', header: { text: '행정구역' }, - items: ['SIDO_CD', 'ADST_CD'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, - 'SLG_FCT_NM', + 'fcltNm', { name: 'group_processing_fac_operating', header: { text: '처리시설 운영현황' }, items: [ - 'TRT_METHOD', - 'SLG_FCT_CPC', - 'OPR_CPC', - 'ODY_RN_TIME', - 'YRSTP_RN_DYN', - 'OPR_RT' + 'prcsMthExpln', + 'fcltCpct', + 'anlPrcsQnt', + 'dayAvgOprtngHr', + 'anlOprtngDayCnt', + 'oprtngRt' ] }, - 'SLG_FCT_RMK' + 'rmrk', + 'admdstCd', + 'admdstNm', + 'fcltSn', ], rowStartNum: 2, fixedColumns: 1, - tabKey: 'sewageSlurgeProcessingFacStatusInp', + tabKey: 'ST00110003', tabLabel: '하수찌꺼기 처리시설 운영현황' }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn.ts b/apps/st/src/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn.ts index 364c4e378..1d3e11f0d 100644 --- a/apps/st/src/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn.ts @@ -5,212 +5,357 @@ import { ValueType } from "realgrid"; export const SlurgeProcessingFacStatusInpColumn = { columns: [ // 행정구역 - { name: 'SIDO_CD', fieldName: '시도', header: { text: '시도' }, editable: false }, - { name: 'ADST_CD', fieldName: '시군구', header: { text: '시군구' }, footer: { text: '합계' } }, + { 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: 'BSC_FCT_NM', fieldName: '하수처리장명', header: { text: '하수처리장명' }, }, + { name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false, styleName: 'rg-cell-readonly' }, // 찌꺼기발생량(톤/년, 탈수기준) - { name: 'TOT_AMNT', fieldName: '합계', header: { text: '합계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'P_AMNT', fieldName: '자체', header: { text: '자체' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'OUT_AMNT', fieldName: '외부유입량', header: { text: '외부유입량' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'gnamWhol', + fieldName: 'gnamWhol', + header: { text: '합계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: ['itslfQnt', 'otsdInflwQnt'], + type: 'sum' as const, + }, + }, + { name: 'itslfQnt', fieldName: 'itslfQnt', header: { text: '자체' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'otsdInflwQnt', fieldName: 'otsdInflwQnt', header: { text: '외부유입량' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 이송처리장 - { name: 'OUT_FCT_NM', fieldName: '이송처리장', header: { text: '이송처리장' } }, + { name: 'transfTmplntNm', fieldName: 'transfTmplntNm', header: { text: '이송처리장' } }, // 이송처리량 - 쿼리에서 못찾음 (임의) - { name: 'a', fieldName: '이송처리량(m³/년)', header: { text: '이송처리량(m³/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'transfPrcsQnt', fieldName: 'transfPrcsQnt', header: { text: '이송처리량(m³/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 함수율 - { name: 'DSP_MOIRAT', fieldName: '함수율(%, 탈수기준)', header: { text: '함수율(%, 탈수기준)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'srpFncRt', fieldName: 'srpFncRt', header: { text: '함수율(%, 탈수기준)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 찌꺼기처리량합계 - { name: 'DSP_SM', fieldName: '찌꺼기처리량합계(톤/년)', header: { text: '찌꺼기처리량합계(톤/년)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'wholSrpPrcsQnt', + fieldName: 'wholSrpPrcsQnt', + header: { text: '찌꺼기처리량합계(톤/년)' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'itslfTs1RclmQnt', + 'itslfTs1IncnrQnt', + 'itslfTs1AltdQnt', + 'itslfTs1DryQnt', + 'itslfTs1FuelQnt', + 'itslfTs2CmpstgQnt', + 'otsdCnsgnRclmQnt', + 'otsdCnsgnIncnrQnt', + 'otsdCnsgnFuelPwplQnt', + 'otsdCnsgnPrdctRmtrlQnt', + 'otsdCnsgnFmlyrQnt', + 'otsdCnsgnCmpstgQnt', + 'cparTs1Whol', + ], + type: 'sum' as const, + } + }, // 자체 찌꺼기 처리량 > 자체찌꺼기처리량소계 - { name: 'DSP_SELF_SM', fieldName: '자체찌꺼기처리량소계', header: { text: '자체찌꺼기처리량소계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'itslfTs1Whol', + fieldName: 'itslfTs1Whol', + header: { text: '자체찌꺼기처리량소계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'itslfTs1RclmQnt', + 'itslfTs1IncnrQnt', + 'itslfTs1AltdQnt', + 'itslfTs1DryQnt', + 'itslfTs1FuelQnt', + 'itslfTs2CmpstgQnt', + ], + type: 'sum' as const, + } + }, // 자체 찌꺼기 처리량 > 매립 - { name: 'O_DSP_RCLM', fieldName: '매립', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'itslfTs1RclmQnt', fieldName: 'itslfTs1RclmQnt', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 자체 찌꺼기 처리량 > 소각(1차) - { name: 'O_DSP_INCNR', fieldName: '소각(1차)', header: { text: '소각(1차)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'itslfTs1IncnrQnt', fieldName: 'itslfTs1IncnrQnt', header: { text: '소각(1차)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 자체 찌꺼기 처리량 > 소각 후 처리(2차) - 쿼리에 없음 (임의 생성) - { name: 'b1', fieldName: '소계', header: { text: '소계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'b2', fieldName: '매립', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'b3', fieldName: '제품원료', header: { text: '제품원료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'b4', fieldName: '복토재', header: { text: '복토재' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'b5', fieldName: '시설(업체명)', header: { text: '시설(업체명)' } }, + { + name: 'incnrPrcsWhol', + fieldName: 'incnrPrcsWhol', + header: { text: '소계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'incnrPrcsLfvlm', + 'incnrPrcsPrdctRmtrlQnt', + 'incnrPrcsFmlyrQnt', + ], + type: 'sum' as const, + } + }, + { name: 'incnrPrcsLfvlm', fieldName: 'incnrPrcsLfvlm', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'incnrPrcsPrdctRmtrlQnt', fieldName: 'incnrPrcsPrdctRmtrlQnt', header: { text: '제품원료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'incnrPrcsFmlyrQnt', fieldName: 'incnrPrcsFmlyrQnt', header: { text: '복토재' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'incnrPrcsFcltNm', fieldName: 'incnrPrcsFcltNm', header: { text: '시설(업체명)' } }, // 자체 찌꺼기 처리량 > 고화(1차) - 쿼리에 없음 (임의 생성) - { name: 'c1', fieldName: '고화(1차)', header: { text: '고화(1차)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'itslfTs1AltdQnt', fieldName: 'itslfTs1AltdQnt', header: { text: '고화(1차)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 자체 찌꺼기 처리량 > 고화 후 처리(2차) - 쿼리에 없음 (임의 생성) - { name: 'c2', fieldName: '소계', header: { text: '소계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'c3', fieldName: '매립', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'c4', fieldName: '복토재', header: { text: '복토재' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'c5', fieldName: '시설(업체명)', header: { text: '시설(업체명)' } }, + { + name: 'stbznPrcsWhol', + fieldName: 'stbznPrcsWhol', + header: { text: '소계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'stbznPrcsLfvlm', + 'stbznPrcsFmlyrQnt', + ], + type: 'sum' as const, + } + }, + { name: 'stbznPrcsLfvlm', fieldName: 'stbznPrcsLfvlm', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'stbznPrcsFmlyrQnt', fieldName: 'stbznPrcsFmlyrQnt', header: { text: '복토재' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'stbznPrcsFcltNm', fieldName: 'stbznPrcsFcltNm', header: { text: '시설(업체명)' } }, // 자체 찌꺼기 처리량 > 건조(1차) - { name: 'DSP_DESIC', fieldName: '건조(1차)', header: { text: '건조(1차)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'itslfTs1DryQnt', fieldName: 'itslfTs1DryQnt', header: { text: '건조(1차)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 자체 찌꺼기 처리량 > 건조 후 처리(2차) - 쿼리에 없음 - { name: 'd1', fieldName: '소계', header: { text: '소계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'd2', fieldName: '매립', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'd3', fieldName: '소각', header: { text: '소각' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'd4', fieldName: '연료', header: { text: '연료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'd5', fieldName: '제품원료', header: { text: '제품원료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'd6', fieldName: '복토재', header: { text: '복토재' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'd7', fieldName: '퇴비화', header: { text: '퇴비화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'd8', fieldName: '시설(업체명)', header: { text: '시설(업체명)' }, editable: true }, + { + name: 'dryPrcsWhol', + fieldName: 'dryPrcsWhol', + header: { text: '소계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'dryPrcsLfvlm', + 'dryPrcsIncnrQnt', + 'dryPrcsFuelQnt', + 'dryPrcsPrdctRmtrlQnt', + 'dryPrcsFmlyrQnt', + 'dryPrcsCmpstgQnt', + ], + type: 'sum' as const, + } + }, + { name: 'dryPrcsLfvlm', fieldName: 'dryPrcsLfvlm', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'dryPrcsIncnrQnt', fieldName: 'dryPrcsIncnrQnt', header: { text: '소각' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'dryPrcsFuelQnt', fieldName: 'dryPrcsFuelQnt', header: { text: '연료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'dryPrcsPrdctRmtrlQnt', fieldName: 'dryPrcsPrdctRmtrlQnt', header: { text: '제품원료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'dryPrcsFmlyrQnt', fieldName: 'dryPrcsFmlyrQnt', header: { text: '복토재' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'dryPrcsCmpstgQnt', fieldName: 'dryPrcsCmpstgQnt', header: { text: '퇴비화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'dryPrcsFcltNm', fieldName: 'dryPrcsFcltNm', header: { text: '시설(업체명)' }, editable: true }, // 자체 찌꺼기 처리량 > 탄화(1차) - 쿼리에 없음 (임의 생성) - { name: 'e1', fieldName: '탄화(1차)', header: { text: '탄화(1차)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'itslfTs1FuelQnt', fieldName: 'itslfTs1FuelQnt', header: { text: '탄화(1차)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 자체 찌꺼기 처리량 > 탄화 후 처리(2차) - 쿼리에 없음 (임의 생성) - { name: 'e2', fieldName: '소계', header: { text: '소계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'e3', fieldName: '소각', header: { text: '소각' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'e4', fieldName: '연료', header: { text: '연료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'e5', fieldName: '시설(업체명)', header: { text: '시설(업체명)' } }, + { + name: 'fuelPrcsWhol', + fieldName: 'fuelPrcsWhol', + header: { text: '소계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: ['fuelPrcsIncnrQnt', 'fuelPrcsFuelQnt'], + type: 'sum' as const, + } + }, + { name: 'fuelPrcsIncnrQnt', fieldName: 'fuelPrcsIncnrQnt', header: { text: '소각' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'fuelPrcsFuelQnt', fieldName: 'fuelPrcsFuelQnt', header: { text: '연료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'fuelPrcsFcltNm', fieldName: 'fuelPrcsFcltNm', header: { text: '시설(업체명)' } }, // 자체 찌꺼기 처리량 > 퇴비화 - 쿼리에 없음 (임의 생성) - { name: 'f1', fieldName: '퇴비화', header: { text: '퇴비화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'itslfTs2CmpstgQnt', fieldName: 'itslfTs2CmpstgQnt', header: { text: '퇴비화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 자체 찌꺼기 처리량 > 퇴비화 > 시설(업체명) - 쿼리에 없음 (임의 생성) - { name: 'f2', fieldName: '시설(업체명)', header: { text: '시설(업체명)' } }, + { name: 'cmpsPrcsFcltBzentyNm', fieldName: 'cmpsPrcsFcltBzentyNm', header: { text: '시설(업체명)' } }, // 외부위탁 처리량(톤/년) - { name: 'DSP_CONSIGNMENT', fieldName: '소계', header: { text: '소계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'g1', fieldName: '매립', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'g2', fieldName: '소각', header: { text: '소각' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'g3', fieldName: '연료', header: { text: '연료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'g4', fieldName: '제품원료', header: { text: '제품원료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'g5', fieldName: '복토재', header: { text: '복토재' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'g6', fieldName: '퇴비화', header: { text: '퇴비화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'g7', fieldName: '시설(업체명)', header: { text: '시설(업체명)' } }, + { + name: 'otsdCnsgnWhol', + fieldName: 'otsdCnsgnWhol', + header: { text: '소계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'otsdCnsgnRclmQnt', + 'otsdCnsgnIncnrQnt', + 'otsdCnsgnFuelPwplQnt', + 'otsdCnsgnPrdctRmtrlQnt', + 'otsdCnsgnFmlyrQnt', + 'otsdCnsgnCmpstgQnt', + ], + type: 'sum' as const, + } + }, + { name: 'otsdCnsgnRclmQnt', fieldName: 'otsdCnsgnRclmQnt', header: { text: '매립' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'otsdCnsgnIncnrQnt', fieldName: 'otsdCnsgnIncnrQnt', header: { text: '소각' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'otsdCnsgnFuelPwplQnt', fieldName: 'otsdCnsgnFuelPwplQnt', header: { text: '연료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'otsdCnsgnPrdctRmtrlQnt', fieldName: 'otsdCnsgnPrdctRmtrlQnt', header: { text: '제품원료' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'otsdCnsgnFmlyrQnt', fieldName: 'otsdCnsgnFmlyrQnt', header: { text: '복토재' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'otsdCnsgnCmpstgQnt', fieldName: 'otsdCnsgnCmpstgQnt', header: { text: '퇴비화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'otsdCnsgnFcltNm', fieldName: 'otsdCnsgnFcltNm', header: { text: '시설(업체명)' } }, // 수도권광역위탁처리량 - { name: 'DSP_WIDE', fieldName: '수도권광역위탁처리량(톤/년)', header: { text: '수도권광역위탁처리량(톤/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'cparTs1Whol', fieldName: 'cparTs1Whol', header: { text: '수도권광역위탁처리량(톤/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 비고 - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - // Hidden: Check fields - { name: 'CHK_TOT_AMNT', fieldName: 'CHK_TOT_AMNT', header: { text: 'CHK_TOT_AMNT' }, visible: false }, - { name: 'CHK_P_AMNT', fieldName: 'CHK_P_AMNT', header: { text: 'CHK_P_AMNT' }, visible: false }, - { name: 'CHK_OUT_AMNT', fieldName: 'CHK_OUT_AMNT', header: { text: 'CHK_OUT_AMNT' }, visible: false }, - { name: 'CHK_DSP_MOIRAT', fieldName: 'CHK_DSP_MOIRAT', header: { text: 'CHK_DSP_MOIRAT' }, visible: false }, - { name: 'CHK_DSP_SM', fieldName: 'CHK_DSP_SM', header: { text: 'CHK_DSP_SM' }, visible: false }, - { name: 'CHK_DSP_SELF_SM', fieldName: 'CHK_DSP_SELF_SM', header: { text: 'CHK_DSP_SELF_SM' }, visible: false }, - { name: 'CHK_O_DSP_RCLM', fieldName: 'CHK_O_DSP_RCLM', header: { text: 'CHK_O_DSP_RCLM' }, visible: false }, - { name: 'CHK_O_DSP_INCNR', fieldName: 'CHK_O_DSP_INCNR', header: { text: 'CHK_O_DSP_INCNR' }, visible: false }, - { name: 'CHK_DSP_DESIC', fieldName: 'CHK_DSP_DESIC', header: { text: 'CHK_DSP_DESIC' }, visible: false }, - { name: 'CHK_DSP_CONSIGNMENT', fieldName: 'CHK_DSP_CONSIGNMENT', header: { text: 'CHK_DSP_CONSIGNMENT' }, visible: false }, - { name: 'CHK_DSP_WIDE', fieldName: 'CHK_DSP_WIDE', header: { text: 'CHK_DSP_WIDE' }, visible: false }, - { name: 'CHK_OUT_FCT_NM', fieldName: 'CHK_OUT_FCT_NM', header: { text: 'CHK_OUT_FCT_NM' }, visible: false }, + // hidden + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false }, + { name: 'fcltSn', fieldName: 'fcltSn', header: { text: '시설물일련번호' }, visible: false }, + { name: 'swtrmtFcltCd', fieldName: 'swtrmtFcltCd', header: { text: '처리장코드' }, visible: false }, ], columnLayout: [ { name: 'group_admin', header: { text: '행정구역' }, - items: ['SIDO_CD', 'ADST_CD'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, - 'BSC_FCT_NM', + 'swtrmtFcltNm', { name: 'group_occur_amount', header: { text: '찌꺼기발생량(톤/년, 탈수기준)' }, items: [ - 'TOT_AMNT', - 'P_AMNT', - 'OUT_AMNT' + 'gnamWhol', + 'itslfQnt', + 'otsdInflwQnt' ] }, - 'OUT_FCT_NM', - 'a', - 'DSP_MOIRAT', - 'DSP_SM', + 'transfTmplntNm', + 'transfPrcsQnt', + 'srpFncRt', + 'wholSrpPrcsQnt', { name: 'group_self_treatment', header: { text: '자체 찌꺼기 처리량(톤/년)' }, items: [ - 'DSP_SELF_SM', - 'O_DSP_RCLM', - 'O_DSP_INCNR', + 'itslfTs1Whol', + 'itslfTs1RclmQnt', + 'itslfTs1IncnrQnt', { name: 'group_incinerate_2nd', header: { text: '소각 후 처리(2차)' }, items: [ - 'b1', - 'b2', - 'b3', - 'b4', - 'b5' + 'incnrPrcsWhol', + 'incnrPrcsLfvlm', + 'incnrPrcsPrdctRmtrlQnt', + 'incnrPrcsFmlyrQnt', + 'incnrPrcsFcltNm' ] }, - 'c1', + 'itslfTs1AltdQnt', { name: 'group_solidify_2nd', header: { text: '고화 후 처리(2차)' }, items: [ - 'c2', - 'c3', - 'c4', - 'c5' + 'stbznPrcsWhol', + 'stbznPrcsLfvlm', + 'stbznPrcsFmlyrQnt', + 'stbznPrcsFcltNm' ] }, - 'DSP_DESIC', + 'itslfTs1DryQnt', { name: 'group_drying_2nd', header: { text: '건조 후 처리(2차)' }, items: [ - 'd1', - 'd2', - 'd3', - 'd4', - 'd5', - 'd6', - 'd7', - 'd8' + 'dryPrcsWhol', + 'dryPrcsLfvlm', + 'dryPrcsIncnrQnt', + 'dryPrcsFuelQnt', + 'dryPrcsPrdctRmtrlQnt', + 'dryPrcsFmlyrQnt', + 'dryPrcsCmpstgQnt', + 'dryPrcsFcltNm' ] }, - 'e1', + 'itslfTs1FuelQnt', { name: 'group_carbonize_2nd', header: { text: '탄화 후 처리(2차)' }, items: [ - 'e2', - 'e3', - 'e4', - 'e5' + 'fuelPrcsWhol', + 'fuelPrcsIncnrQnt', + 'fuelPrcsFuelQnt', + 'fuelPrcsFcltNm' ] }, - 'f1', - 'f2' + 'itslfTs2CmpstgQnt', + 'cmpsPrcsFcltBzentyNm' ] }, { name: 'group_external_treatment', header: { text: '외부위탁 처리량(톤/년)' }, items: [ - 'DSP_CONSIGNMENT', - 'g1', - 'g2', - 'g3', - 'g4', - 'g5', - 'g6', - 'g7' + 'otsdCnsgnWhol', + 'otsdCnsgnRclmQnt', + 'otsdCnsgnIncnrQnt', + 'otsdCnsgnFuelPwplQnt', + 'otsdCnsgnPrdctRmtrlQnt', + 'otsdCnsgnFmlyrQnt', + 'otsdCnsgnCmpstgQnt', + 'otsdCnsgnFcltNm' ] }, - 'DSP_WIDE', - 'RMK' + 'cparTs1Whol', + 'rmrk', + 'admdstCd', + 'admdstNm', + 'fcltSn', + 'swtrmtFcltCd' ], rowStartNum: 3, fixedColumns: 2, - tabKey: 'slurgeProcessingFacStatusInp', + tabKey: 'ST00110001', tabLabel: '찌꺼기(슬러지)처리시설현황' }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/waterReuse/PublicSewageTreatWaterReuseInpColumn.ts b/apps/st/src/components/util/column/input/waterReuse/PublicSewageTreatWaterReuseInpColumn.ts index db1a72a59..709cc473d 100644 --- a/apps/st/src/components/util/column/input/waterReuse/PublicSewageTreatWaterReuseInpColumn.ts +++ b/apps/st/src/components/util/column/input/waterReuse/PublicSewageTreatWaterReuseInpColumn.ts @@ -31,6 +31,25 @@ function dropdownColumn( }; } +/** 하수처리수 재이용현황(천톤/년) — 장내·장외 세부 항목 합산 필드 */ +const PUBLIC_SEWAGE_REUSE_USE_QNT_SUM_FIELDS = [ + 'ithlClnQty', + 'ithlFrzgQty', + 'ithlLdhcUswtrQnt', + 'ithlDltnUswtrQnt', + 'ithlRuswtrQnt', + 'ithlEtcQnt', + 'ithlUnsdQnt', + 'otcntrClnRstrmQnt', + 'otcntrWshwtrQnt', + 'otcntrLdhcUswtrQnt', + 'otcntrHdrplUswtrQnt', + 'otcntrRvrKeepQnt', + 'agrcltUswtrPstnQnt', + 'ugwtrChargQnt', + 'mdsrUswtrQnt', +] as const; + // 하수도 통계 관리 > 10. 물 재이용 > tab 1: 공공하수처리수 재이용 export const PublicSewageTreatWaterReuseInpColumn = { columns: [ @@ -102,10 +121,44 @@ export const PublicSewageTreatWaterReuseInpColumn = { { name: 'ruswtPumpLen', fieldName: 'ruswtPumpLen', header: { text: '공급펌프(톤/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 하수처리수 재이용현황 > 총계 - { name: 'wholUseQnt', fieldName: 'wholUseQnt', header: { text: '총계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'wholUseQnt', + fieldName: 'wholUseQnt', + header: { text: '총계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [...PUBLIC_SEWAGE_REUSE_USE_QNT_SUM_FIELDS], + type: 'sum' as const, + }, + }, // 하수처리수 재이용현황 > 장내용수 - { name: 'ithlWshwtrQnt', fieldName: 'ithlWshwtrQnt', header: { text: '계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'ithlWshwtrQnt', + fieldName: 'ithlWshwtrQnt', + header: { text: '계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'ithlClnQty', + 'ithlFrzgQty', + 'ithlLdhcUswtrQnt', + 'ithlDltnUswtrQnt', + 'ithlRuswtrQnt', + 'ithlEtcQnt', + 'ithlUnsdQnt', + ], + type: 'sum' as const, + }, + }, { name: 'ithlClnQty', fieldName: 'ithlClnQty', header: { text: '세척수' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly', editable: false }, { name: 'ithlFrzgQty', fieldName: 'ithlFrzgQty', header: { text: '냉각수' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly', editable: false }, { name: 'ithlLdhcUswtrQnt', fieldName: 'ithlLdhcUswtrQnt', header: { text: '청소수' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly', editable: false }, @@ -115,7 +168,29 @@ export const PublicSewageTreatWaterReuseInpColumn = { { name: 'ithlUnsdQnt', fieldName: 'ithlUnsdQnt', header: { text: '장내기타용수' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly', editable: false }, // 하수처리수 재이용현황 > 장외용수 - { name: 'otcntrUswtrWhol', fieldName: 'otcntrUswtrWhol', header: { text: '계' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'otcntrUswtrWhol', + fieldName: 'otcntrUswtrWhol', + header: { text: '계' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + fields: [ + 'otcntrClnRstrmQnt', + 'otcntrWshwtrQnt', + 'otcntrLdhcUswtrQnt', + 'otcntrHdrplUswtrQnt', + 'otcntrRvrKeepQnt', + 'agrcltUswtrPstnQnt', + 'ugwtrChargQnt', + 'mdsrUswtrQnt', + ], + type: 'sum' as const, + }, + }, { name: 'otcntrClnRstrmQnt', fieldName: 'otcntrClnRstrmQnt', header: { text: '청소·화장실용수' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly', editable: false }, { name: 'otcntrWshwtrQnt', fieldName: 'otcntrWshwtrQnt', header: { text: '세척·살수용수' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly', editable: false }, { name: 'otcntrLdhcUswtrQnt', fieldName: 'otcntrLdhcUswtrQnt', header: { text: '조경용수' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly', editable: false }, @@ -126,7 +201,21 @@ export const PublicSewageTreatWaterReuseInpColumn = { { name: 'mdsrUswtrQnt', fieldName: 'mdsrUswtrQnt', header: { text: '공업용수' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly', editable: false }, // 재이용률 - { name: 'rtlztnRt', fieldName: 'rtlztnRt', header: { text: '재이용률(%)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { + name: 'rtlztnRt', + fieldName: 'rtlztnRt', + header: { text: '재이용률(%)' }, + editable: false, + footer: footerDecimal1, + valueType: ValueType.NUMBER, + dataFormat: '#,##0.0', + styleName: 'rg-cell-total', + computed: { + numeratorFields: [...PUBLIC_SEWAGE_REUSE_USE_QNT_SUM_FIELDS], + denominatorFields: ['wholPrcsQnt'], + type: 'pblswrgPopltnSuprtRate' as const, + }, + }, // 비고 { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, @@ -142,7 +231,10 @@ export const PublicSewageTreatWaterReuseInpColumn = { { name: 'group_admin', header: { text: '행정구역' }, - items: ['ctpvNm', 'sggNm'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, { name: 'group_facility', diff --git a/apps/st/src/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn.ts b/apps/st/src/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn.ts index 9455637b0..f5189c746 100644 --- a/apps/st/src/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn.ts @@ -139,7 +139,10 @@ export const RainFallUtilFactStatusInpColumn = { { name: 'group_admin', header: { text: '행정구역' }, - items: ['ctpvNm', 'sggNm'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, 'fcltNm', 'bdstTypCn', diff --git a/apps/st/src/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn.ts b/apps/st/src/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn.ts index ee62e6c82..4930c51d2 100644 --- a/apps/st/src/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn.ts @@ -157,7 +157,10 @@ export const RecycledWaterStatusInpColumn = { { name: 'group_admin', header: { text: '행정구역' }, - items: ['ctpvNm', 'sggNm'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, 'fcltNm', 'bldgSeCn', diff --git a/apps/st/src/components/util/column/input/waterReuse/TreatWaterReuseBusinessApprStatusInpColumn.ts b/apps/st/src/components/util/column/input/waterReuse/TreatWaterReuseBusinessApprStatusInpColumn.ts index 7a31a2162..0a2225dc8 100644 --- a/apps/st/src/components/util/column/input/waterReuse/TreatWaterReuseBusinessApprStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/waterReuse/TreatWaterReuseBusinessApprStatusInpColumn.ts @@ -40,7 +40,10 @@ export const TreatWaterReuseBusinessApprStatusInpColumn = { { name: 'group_admin', header: { text: '행정구역' }, - items: ['ctpvNm', 'sggNm'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, 'bplcNm', 'rprsvNm', diff --git a/apps/st/src/components/util/column/input/waterReuse/TreatWaterReuseDesignConstStatusInpColumn.ts b/apps/st/src/components/util/column/input/waterReuse/TreatWaterReuseDesignConstStatusInpColumn.ts index 4b93706a1..69fc6311c 100644 --- a/apps/st/src/components/util/column/input/waterReuse/TreatWaterReuseDesignConstStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/waterReuse/TreatWaterReuseDesignConstStatusInpColumn.ts @@ -40,7 +40,10 @@ export const TreatWaterReuseDesignConstStatusInpColumn = { { name: 'group_admin', header: { text: '행정구역' }, - items: ['ctpvNm', 'sggNm'] + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }] }, + 'sggNm', + ], }, 'bplcNm', 'rprsvNm', diff --git a/apps/st/src/components/util/grid/computedFieldCallbacks.ts b/apps/st/src/components/util/grid/computedFieldCallbacks.ts index 43ce816f6..0506bee8a 100644 --- a/apps/st/src/components/util/grid/computedFieldCallbacks.ts +++ b/apps/st/src/components/util/grid/computedFieldCallbacks.ts @@ -328,6 +328,25 @@ export function createQuotientValueCallback(fields: string[]): DataFieldValueCal }; } +/** (numerator / denominator) × multiplier × 100 — 분모 0이면 0 */ +export function createQuotientTimesMultiplierPercentValueCallback( + numeratorField: string, + denominatorField: string, + multiplierField: string, +): DataFieldValueCallback { + return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => { + const getVal = (field: string) => { + const idx = fieldNamesArr.indexOf(field); + return idx >= 0 ? toNum(values[idx]) : 0; + }; + const denominator = getVal(denominatorField); + if (denominator === 0) return 0; + const quotient = getVal(numeratorField) / denominator; + const multiplier = getVal(multiplierField); + return truncateToFirstDecimal(quotient * multiplier * 100); + }; +} + /** 연산 타입에 맞는 valueCallback 반환 */ export function getComputedValueCallback(config: ComputedFieldConfig): DataFieldValueCallback { switch (config.type) { @@ -384,5 +403,11 @@ export function getComputedValueCallback(config: ComputedFieldConfig): DataField config.numeratorFields, config.denominatorField, ); + case 'quotientTimesMultiplierPercent': + return createQuotientTimesMultiplierPercentValueCallback( + config.numeratorField, + config.denominatorField, + config.multiplierField, + ); } } diff --git a/apps/st/src/entities/statistics/api/input/statistics-input-sewage-excreta-related-service.ts b/apps/st/src/entities/statistics/api/input/statistics-input-sewage-excreta-related-service.ts new file mode 100644 index 000000000..61baeca8e --- /dev/null +++ b/apps/st/src/entities/statistics/api/input/statistics-input-sewage-excreta-related-service.ts @@ -0,0 +1,163 @@ +import { stClient } from "@/shared/api/client"; +import { withAuthHeader } from "@/shared/api/with-auth-header"; +import { ENDPOINT } from "@/shared/config"; + +import type { + ExcretaRelatedIndustryApiResponse, + ExcretaRelatedIndustrySaveRequest, + ExcretaRelatedIndustrySubmitRequest, + PrivateSewageTreatRelatedIndustryApiResponse, + PrivateSewageTreatRelatedIndustrySaveRequest, + PrivateSewageTreatRelatedIndustrySubmitRequest, + PublicInstitutionExcretaIndustryApiResponse, + PublicInstitutionExcretaIndustrySaveRequest, + PublicInstitutionExcretaIndustrySubmitRequest, + PublicInstitutionSewerIndustryApiResponse, + PublicInstitutionSewerIndustrySaveRequest, + PublicInstitutionSewerIndustrySubmitRequest, + SewageExcretaRelatedRequestParams, + SewageExcretaRelatedSaveResponse, + SewageExcretaRelatedSubmitResponse, + StatisticsInputSewageExcretaRelatedService, +} from "../../model/input/statistics-input-sewage-excreta-related-interface"; + +const BASE = `${ENDPOINT.ST_SERVICE.INPUT}/sewage-excreta-related`; + +export class StatisticsInputSewageExcretaRelatedApiService + implements StatisticsInputSewageExcretaRelatedService +{ + /** 공공기관 하수분야 조회 — ST00120001 */ + public async getPublicInstitutionSewerIndustryList( + params: SewageExcretaRelatedRequestParams, + ): Promise { + const response = await stClient.get( + `${BASE}/public-institution-sewer-industry`, + withAuthHeader({ params }), + ); + return response.data; + } + + /** 공공기관 하수분야 저장 — ST00120001 */ + public async savePublicInstitutionSewerIndustry( + request: PublicInstitutionSewerIndustrySaveRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/public-institution-sewer-industry/save`, + request, + ); + return response.data; + } + + /** 공공기관 하수분야 제출 — ST00120001 */ + public async submitPublicInstitutionSewerIndustry( + request: PublicInstitutionSewerIndustrySubmitRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/public-institution-sewer-industry/submit`, + request, + ); + return response.data; + } + + /** 공공기관 분뇨분야 조회 — ST00120002 */ + public async getPublicInstitutionExcretaIndustryList( + params: SewageExcretaRelatedRequestParams, + ): Promise { + const response = await stClient.get( + `${BASE}/public-institution-excreta-industry`, + withAuthHeader({ params }), + ); + return response.data; + } + + /** 공공기관 분뇨분야 저장 — ST00120002 */ + public async savePublicInstitutionExcretaIndustry( + request: PublicInstitutionExcretaIndustrySaveRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/public-institution-excreta-industry/save`, + request, + ); + return response.data; + } + + /** 공공기관 분뇨분야 제출 — ST00120002 */ + public async submitPublicInstitutionExcretaIndustry( + request: PublicInstitutionExcretaIndustrySubmitRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/public-institution-excreta-industry/submit`, + request, + ); + return response.data; + } + + /** 분뇨수집·운반업 조회 — ST00120003 */ + public async getExcretaRelatedIndustryList( + params: SewageExcretaRelatedRequestParams, + ): Promise { + const response = await stClient.get( + `${BASE}/excreta-related-industry`, + withAuthHeader({ params }), + ); + return response.data; + } + + /** 분뇨수집·운반업 저장 — ST00120003 */ + public async saveExcretaRelatedIndustry( + request: ExcretaRelatedIndustrySaveRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/excreta-related-industry/save`, + request, + ); + return response.data; + } + + /** 분뇨수집·운반업 제출 — ST00120003 */ + public async submitExcretaRelatedIndustry( + request: ExcretaRelatedIndustrySubmitRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/excreta-related-industry/submit`, + request, + ); + return response.data; + } + + /** 개인하수처리관련업 조회 — ST00120004 */ + public async getPrivateSewageTreatRelatedIndustryList( + params: SewageExcretaRelatedRequestParams, + ): Promise { + const response = await stClient.get( + `${BASE}/private-sewage-treat-related-industry`, + withAuthHeader({ params }), + ); + return response.data; + } + + /** 개인하수처리관련업 저장 — ST00120004 */ + public async savePrivateSewageTreatRelatedIndustry( + request: PrivateSewageTreatRelatedIndustrySaveRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/private-sewage-treat-related-industry/save`, + request, + ); + return response.data; + } + + /** 개인하수처리관련업 제출 — ST00120004 */ + public async submitPrivateSewageTreatRelatedIndustry( + request: PrivateSewageTreatRelatedIndustrySubmitRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/private-sewage-treat-related-industry/submit`, + request, + ); + return response.data; + } +} + +export const statisticsInputSewageExcretaRelatedApi = + new StatisticsInputSewageExcretaRelatedApiService(); diff --git a/apps/st/src/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service.ts b/apps/st/src/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service.ts new file mode 100644 index 000000000..4d47e0e24 --- /dev/null +++ b/apps/st/src/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service.ts @@ -0,0 +1,182 @@ +import { stClient } from "@/shared/api/client"; +import { withAuthHeader } from "@/shared/api/with-auth-header"; +import { ENDPOINT } from "@/shared/config"; + +import type { + DigestorOperatingStatusApiResponse, + DigestorOperatingStatusSaveRequest, + DigestorOperatingStatusSaveResponse, + DigestorOperatingStatusSubmitRequest, + DigestorOperatingStatusSubmitResponse, + ExcretaSlurgeProcessingStatusApiResponse, + ExcretaSlurgeProcessingStatusSaveRequest, + ExcretaSlurgeProcessingStatusSaveResponse, + ExcretaSlurgeProcessingStatusSubmitRequest, + ExcretaSlurgeProcessingStatusSubmitResponse, + PublicSewageFacilitiesListApiResponse, + PublicSewageFacilitiesListRequest, + SewageExcretaSlurgeRequestParams, + SewageSlurgeProcessingFacStatusApiResponse, + SewageSlurgeProcessingFacStatusSaveRequest, + SewageSlurgeProcessingFacStatusSaveResponse, + SewageSlurgeProcessingFacStatusSubmitRequest, + SewageSlurgeProcessingFacStatusSubmitResponse, + SlurgeProcessingFacStatusApiResponse, + SlurgeProcessingFacStatusSaveRequest, + SlurgeProcessingFacStatusSaveResponse, + SlurgeProcessingFacStatusSubmitRequest, + SlurgeProcessingFacStatusSubmitResponse, + StatisticsInputSewageExcretaSlurgeService, +} from "../../model/input/statistics-input-sewage-excreta-slurge-interface"; + +const BASE = `${ENDPOINT.ST_SERVICE.INPUT}/sewage-excreta-slurge`; + +export class StatisticsInputSewageExcretaSlurgeApiService + implements StatisticsInputSewageExcretaSlurgeService +{ + /** 찌꺼기(슬러지)처리시설현황 조회 — ST00110001 */ + public async getSlurgeProcessingFacStatusList( + params: SewageExcretaSlurgeRequestParams, + ): Promise { + const response = await stClient.get( + `${BASE}/slurge-processing-fac-status`, + withAuthHeader({ params }), + ); + return response.data; + } + + /** 찌꺼기(슬러지)처리시설현황 저장 — ST00110001 */ + public async saveSlurgeProcessingFacStatus( + request: SlurgeProcessingFacStatusSaveRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/slurge-processing-fac-status/save`, + request, + ); + return response.data; + } + + /** 찌꺼기(슬러지)처리시설현황 제출 — ST00110001 */ + public async submitSlurgeProcessingFacStatus( + request: SlurgeProcessingFacStatusSubmitRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/slurge-processing-fac-status/submit`, + request, + ); + return response.data; + } + + /** 소화조 운영현황 조회 — ST00110002 */ + public async getDigestorOperatingStatusList( + params: SewageExcretaSlurgeRequestParams, + ): Promise { + const response = await stClient.get( + `${BASE}/digestor-operating-status`, + withAuthHeader({ params }), + ); + return response.data; + } + + /** 소화조 운영현황 저장 — ST00110002 */ + public async saveDigestorOperatingStatus( + request: DigestorOperatingStatusSaveRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/digestor-operating-status/save`, + request, + ); + return response.data; + } + + /** 소화조 운영현황 제출 — ST00110002 */ + public async submitDigestorOperatingStatus( + request: DigestorOperatingStatusSubmitRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/digestor-operating-status/submit`, + request, + ); + return response.data; + } + + /** 하수찌꺼기 처리시설 운영현황 조회 — ST00110003 */ + public async getSewageSlurgeProcessingFacStatusList( + params: SewageExcretaSlurgeRequestParams, + ): Promise { + const response = await stClient.get( + `${BASE}/sewage-slurge-processing-fac-status`, + withAuthHeader({ params }), + ); + return response.data; + } + + /** 하수찌꺼기 처리시설 운영현황 저장 — ST00110003 */ + public async saveSewageSlurgeProcessingFacStatus( + request: SewageSlurgeProcessingFacStatusSaveRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/sewage-slurge-processing-fac-status/save`, + request, + ); + return response.data; + } + + /** 하수찌꺼기 처리시설 운영현황 제출 — ST00110003 */ + public async submitSewageSlurgeProcessingFacStatus( + request: SewageSlurgeProcessingFacStatusSubmitRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/sewage-slurge-processing-fac-status/submit`, + request, + ); + return response.data; + } + + /** 분뇨찌꺼기(슬러지)처리현황 조회 — ST00110004 */ + public async getExcretaSlurgeProcessingStatusList( + params: SewageExcretaSlurgeRequestParams, + ): Promise { + const response = await stClient.get( + `${BASE}/excreta-slurge-processing-status`, + withAuthHeader({ params }), + ); + return response.data; + } + + /** 분뇨찌꺼기(슬러지)처리현황 저장 — ST00110004 */ + public async saveExcretaSlurgeProcessingStatus( + request: ExcretaSlurgeProcessingStatusSaveRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/excreta-slurge-processing-status/save`, + request, + ); + return response.data; + } + + /** 분뇨찌꺼기(슬러지)처리현황 제출 — ST00110004 */ + public async submitExcretaSlurgeProcessingStatus( + request: ExcretaSlurgeProcessingStatusSubmitRequest, + ): Promise { + const response = await stClient.put( + `${BASE}/excreta-slurge-processing-status/submit`, + request, + ); + return response.data; + } + + /** 공공하수처리시설 목록 조회 */ + public async getPublicSewageFacilitiesList( + params: PublicSewageFacilitiesListRequest, + ): Promise { + const response = await stClient.get( + `${BASE}/public-sewage-facilities`, + withAuthHeader({ params }), + ); + return response.data; + } +} + +export const statisticsInputSewageExcretaSlurgeApi = + new StatisticsInputSewageExcretaSlurgeApiService(); diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface.ts new file mode 100644 index 000000000..f2db92d5a --- /dev/null +++ b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface.ts @@ -0,0 +1,328 @@ +import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; + +/** 하수·분뇨관련산업 — 공공기관 하수분야(ST00120001) `PublicInstitutionSewerIndustryInpColumn.tabKey` */ +export const SEWAGE_EXCRETA_PUBLIC_INSTITUTION_SEWER_STATS_CD = "ST00120001" as const; + +/** 검증 결과 한 건 */ +export interface SewageExcretaRelatedVerificationResultItem { + vrfcCd: string; + rsnExpln: string; + nowVlCn: string; + compVlCn: string; +} + +/** 필드명 → 검증 결과 배열 */ +export type SewageExcretaRelatedVerificationResult = Record< + string, + SewageExcretaRelatedVerificationResultItem[] +>; + +/** 조회 `data.commonInfo` */ +export interface SewageExcretaRelatedCommonInfo { + statsYr: string; + ctpvNm: string; + sewerBzmnCd: string; + statsCd: string; + verificationResult?: SewageExcretaRelatedVerificationResult; +} + +/** 하수·분뇨관련산업 조회 요청 파라미터 */ +export interface SewageExcretaRelatedRequestParams { + ctpvNm?: string; + sggNm?: string; + sewerBzmnCd?: string; + statsYr?: string; + mode?: string; +} + +/** + * ST00120001 조회 `data.publicInstitutionSewerIndustryList[]` 한 행. + * `PublicInstitutionSewerIndustryInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 `commonInfo.ctpvNm` 시도명). + */ +export interface PublicInstitutionSewerIndustryItem { + admdstCd: string; + sggNm: string; + admdstNm: string; + ognzSn: string; + ognzNm: string; + empGrssCnt: number; + pbadmsNope: number; + techWholNope: number; + engrkCnt: number; + mchnCnt: number; + prvtmCnt: number; + chmengCnt: number; + envCnt: number; + enjbEtcCnt: number; + etcCnt: number; + rmrk: string | null; + verificationResult?: SewageExcretaRelatedVerificationResult; +} + +export interface PublicInstitutionSewerIndustryResponseData { + statusInfo: SewerageStatusInfo; + commonInfo: SewageExcretaRelatedCommonInfo; + publicInstitutionSewerIndustryList: PublicInstitutionSewerIndustryItem[]; +} + +export type PublicInstitutionSewerIndustryApiResponse = + ApiResponse & { + message: string; + }; + +/** 하수·분뇨관련산업 — 공공기관 분뇨분야(ST00120002) `PublicInstitutionExcretaIndustryInpColumn.tabKey` */ +export const SEWAGE_EXCRETA_PUBLIC_INSTITUTION_EXCRETA_STATS_CD = "ST00120002" as const; + +/** + * ST00120002 조회 `data.publicInstitutionExcretaIndustryList[]` 한 행. + * `PublicInstitutionExcretaIndustryInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 `commonInfo.ctpvNm` 시도명). + */ +export interface PublicInstitutionExcretaIndustryItem { + admdstCd: string; + sggNm: string; + admdstNm: string; + ognzSn: string; + ognzNm: string; + empGrssCnt: number; + pbadmsNope: number; + techWholNope: number; + engrkCnt: number; + mchnCnt: number; + prvtmCnt: number; + chmengCnt: number; + envCnt: number; + enjbEtcCnt: number; + etcCnt: number; + rmrk: string | null; + verificationResult?: SewageExcretaRelatedVerificationResult; +} + +export interface PublicInstitutionExcretaIndustryResponseData { + statusInfo: SewerageStatusInfo; + commonInfo: SewageExcretaRelatedCommonInfo; + publicInstitutionExcretaIndustryList: PublicInstitutionExcretaIndustryItem[]; +} + +export type PublicInstitutionExcretaIndustryApiResponse = + ApiResponse & { + message: string; + }; + +/** 하수·분뇨관련산업 — 분뇨수집·운반업(ST00120003) `ExcretaRelatedIndustryInpColumn.tabKey` */ +export const SEWAGE_EXCRETA_EXCRETA_RELATED_INDUSTRY_STATS_CD = "ST00120003" as const; + +/** + * ST00120003 조회 `data.excretaRelatedIndustryList[]` 한 행. + * `ExcretaRelatedIndustryInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 `commonInfo.ctpvNm` 시도명). + */ +export interface ExcretaRelatedIndustryItem { + admdstCd: string; + sggNm: string; + admdstNm: string; + bzentyCnt: number; + empNope: number; + fcltSecrPrstCnt: number; + n3TonUdrCnt: number; + n4TonUdrCnt: number; + n8TonUdrCnt: number; + etcCnt: number; + clctCnvncPrfmncQnt: number; + rmrk: string | null; + verificationResult?: SewageExcretaRelatedVerificationResult; +} + +export interface ExcretaRelatedIndustryResponseData { + statusInfo: SewerageStatusInfo; + commonInfo: SewageExcretaRelatedCommonInfo; + excretaRelatedIndustryList: ExcretaRelatedIndustryItem[]; +} + +export type ExcretaRelatedIndustryApiResponse = ApiResponse & { + message: string; +}; + +/** 하수·분뇨관련산업 — 개인하수처리관련업(ST00120004) `PrivateSewageTreatRelatedIndustryInpColumn.tabKey` */ +export const SEWAGE_EXCRETA_PRIVATE_SEWAGE_TREAT_RELATED_INDUSTRY_STATS_CD = "ST00120004" as const; + +/** + * ST00120004 조회 `data.privateSewageTreatRelatedIndustryList[]` 한 행. + * `PrivateSewageTreatRelatedIndustryInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 `commonInfo.ctpvNm` 시도명). + */ +export interface PrivateSewageTreatRelatedIndustryItem { + admdstCd: string; + sggNm: string; + admdstNm: string; + designMdsrCnt: number; + designMdsrNope: number; + mfbizCnt: number; + mfbizMdsrNope: number; + mngCnt: number; + mngMdsrNope: number; + rmrk: string | null; + verificationResult?: SewageExcretaRelatedVerificationResult; +} + +export interface PrivateSewageTreatRelatedIndustryResponseData { + statusInfo: SewerageStatusInfo; + commonInfo: SewageExcretaRelatedCommonInfo; + privateSewageTreatRelatedIndustryList: PrivateSewageTreatRelatedIndustryItem[]; +} + +export type PrivateSewageTreatRelatedIndustryApiResponse = + ApiResponse & { + message: string; + }; + +/** ST00120001·ST00120002 저장 — `saveList` / `deleteList` 항목 (`sggNm`·계산필드·검증결과 제외) */ +export type PublicInstitutionSewerIndustrySaveItem = Omit< + PublicInstitutionSewerIndustryItem, + "sggNm" | "verificationResult" | "empGrssCnt" | "techWholNope" +>; + +export type PublicInstitutionExcretaIndustrySaveItem = Omit< + PublicInstitutionExcretaIndustryItem, + "sggNm" | "verificationResult" | "empGrssCnt" | "techWholNope" +>; + +/** ST00120003·ST00120004 저장 — `saveList` 항목 (`sggNm`·계산필드·검증결과 제외) */ +export type ExcretaRelatedIndustrySaveItem = Omit< + ExcretaRelatedIndustryItem, + "sggNm" | "verificationResult" | "fcltSecrPrstCnt" +>; + +export type PrivateSewageTreatRelatedIndustrySaveItem = Omit< + PrivateSewageTreatRelatedIndustryItem, + "sggNm" | "verificationResult" +>; + +export interface SewageExcretaRelatedSaveRequestBase { + statsYr: string; + ctpvNm: string; + sggNm: string; + sewerBzmnCd: string; + userId: string; + superAdmin?: string; +} + +export interface PublicInstitutionSewerIndustrySaveRequest extends SewageExcretaRelatedSaveRequestBase { + noApplicable?: boolean; + saveList: PublicInstitutionSewerIndustrySaveItem[]; + deleteList: PublicInstitutionSewerIndustrySaveItem[]; +} + +export interface PublicInstitutionExcretaIndustrySaveRequest extends SewageExcretaRelatedSaveRequestBase { + noApplicable?: boolean; + saveList: PublicInstitutionExcretaIndustrySaveItem[]; + deleteList: PublicInstitutionExcretaIndustrySaveItem[]; +} + +export interface ExcretaRelatedIndustrySaveRequest extends SewageExcretaRelatedSaveRequestBase { + saveList: ExcretaRelatedIndustrySaveItem[]; +} + +export interface PrivateSewageTreatRelatedIndustrySaveRequest extends SewageExcretaRelatedSaveRequestBase { + saveList: PrivateSewageTreatRelatedIndustrySaveItem[]; +} + +export type SewageExcretaRelatedSaveResponse = ApiResponse & { + message?: string; +}; + +export interface SewageExcretaRelatedSubmitRequestBase extends SewageExcretaRelatedSaveRequestBase { + laePicNm: string; + laePicTelNo: string; + laeKbrdrDeptNm: string; + laeKbrdrJbgdNm: string; + laeIdfrNm: string; + laeIdfrTelNo: string; + laeIdfrDeptNm: string; + laeIdfrJbgdNm: string; +} + +export type PublicInstitutionSewerIndustrySubmitItem = PublicInstitutionSewerIndustrySaveItem; + +export interface PublicInstitutionSewerIndustrySubmitRequest extends SewageExcretaRelatedSubmitRequestBase { + noApplicable?: boolean; + submitList: PublicInstitutionSewerIndustrySubmitItem[]; + deleteList: PublicInstitutionSewerIndustrySubmitItem[]; +} + +export type PublicInstitutionExcretaIndustrySubmitItem = PublicInstitutionExcretaIndustrySaveItem; + +export interface PublicInstitutionExcretaIndustrySubmitRequest extends SewageExcretaRelatedSubmitRequestBase { + noApplicable?: boolean; + submitList: PublicInstitutionExcretaIndustrySubmitItem[]; + deleteList: PublicInstitutionExcretaIndustrySubmitItem[]; +} + +export type ExcretaRelatedIndustrySubmitItem = ExcretaRelatedIndustrySaveItem; + +export interface ExcretaRelatedIndustrySubmitRequest extends SewageExcretaRelatedSubmitRequestBase { + submitList: ExcretaRelatedIndustrySubmitItem[]; +} + +export type PrivateSewageTreatRelatedIndustrySubmitItem = PrivateSewageTreatRelatedIndustrySaveItem; + +export interface PrivateSewageTreatRelatedIndustrySubmitRequest extends SewageExcretaRelatedSubmitRequestBase { + submitList: PrivateSewageTreatRelatedIndustrySubmitItem[]; +} + +export interface SewageExcretaRelatedSubmitResponse { + status: string; + message?: string; + data?: { + finalStatus: string; + nvpassed: boolean; + }; +} + +export interface StatisticsInputSewageExcretaRelatedService { + /** 공공기관 하수분야 조회 — ST00120001 */ + getPublicInstitutionSewerIndustryList( + params: SewageExcretaRelatedRequestParams, + ): Promise; + /** 공공기관 하수분야 저장 — ST00120001 */ + savePublicInstitutionSewerIndustry( + request: PublicInstitutionSewerIndustrySaveRequest, + ): Promise; + /** 공공기관 하수분야 제출 — ST00120001 */ + submitPublicInstitutionSewerIndustry( + request: PublicInstitutionSewerIndustrySubmitRequest, + ): Promise; + /** 공공기관 분뇨분야 조회 — ST00120002 */ + getPublicInstitutionExcretaIndustryList( + params: SewageExcretaRelatedRequestParams, + ): Promise; + /** 공공기관 분뇨분야 저장 — ST00120002 */ + savePublicInstitutionExcretaIndustry( + request: PublicInstitutionExcretaIndustrySaveRequest, + ): Promise; + /** 공공기관 분뇨분야 제출 — ST00120002 */ + submitPublicInstitutionExcretaIndustry( + request: PublicInstitutionExcretaIndustrySubmitRequest, + ): Promise; + /** 분뇨수집·운반업 조회 — ST00120003 */ + getExcretaRelatedIndustryList( + params: SewageExcretaRelatedRequestParams, + ): Promise; + /** 분뇨수집·운반업 저장 — ST00120003 */ + saveExcretaRelatedIndustry( + request: ExcretaRelatedIndustrySaveRequest, + ): Promise; + /** 분뇨수집·운반업 제출 — ST00120003 */ + submitExcretaRelatedIndustry( + request: ExcretaRelatedIndustrySubmitRequest, + ): Promise; + /** 개인하수처리관련업 조회 — ST00120004 */ + getPrivateSewageTreatRelatedIndustryList( + params: SewageExcretaRelatedRequestParams, + ): Promise; + /** 개인하수처리관련업 저장 — ST00120004 */ + savePrivateSewageTreatRelatedIndustry( + request: PrivateSewageTreatRelatedIndustrySaveRequest, + ): Promise; + /** 개인하수처리관련업 제출 — ST00120004 */ + submitPrivateSewageTreatRelatedIndustry( + request: PrivateSewageTreatRelatedIndustrySubmitRequest, + ): Promise; +} diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface.ts new file mode 100644 index 000000000..4c575fe84 --- /dev/null +++ b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface.ts @@ -0,0 +1,539 @@ +import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; + +/** 슬러지 처리 — 찌꺼기(슬러지)처리시설현황(ST00110001) `SlurgeProcessingFacStatusInpColumn.tabKey` */ +export const SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD = "ST00110001" as const; + +/** 슬러지 처리 — 소화조 운영현황(ST00110002) `DigestorOperatingStatusInpColumn.tabKey` */ +export const SEWAGE_EXCRETA_DIGESTOR_OPERATING_STATS_CD = "ST00110002" as const; + +/** 슬러지 처리 — 하수찌꺼기 처리시설 운영현황(ST00110003) `SewageSlurgeProcessingFacStatusInpColumn.tabKey` */ +export const SEWAGE_EXCRETA_SEWAGE_SLURGE_FAC_STATUS_STATS_CD = "ST00110003" as const; + +/** 슬러지 처리 — 분뇨찌꺼기(슬러지)처리현황(ST00110004) `ExcretaSlurgeProcessingStatusInpColumn.tabKey` */ +export const SEWAGE_EXCRETA_EXCRETA_SLURGE_PROCESSING_STATS_CD = "ST00110004" as const; + +/** 검증 결과 한 건 */ +export interface SewageExcretaSlurgeVerificationResultItem { + vrfcCd: string; + rsnExpln: string; + nowVlCn: string; + compVlCn: string; +} + +/** 필드명 → 검증 결과 배열 */ +export type SewageExcretaSlurgeVerificationResult = Record< + string, + SewageExcretaSlurgeVerificationResultItem[] +>; + +/** 조회 `data.commonInfo` */ +export interface SewageExcretaSlurgeCommonInfo { + statsYr: string; + ctpvNm: string; + sewerBzmnCd: string; + statsCd: string; + verificationResult?: SewageExcretaSlurgeVerificationResult; +} + +/** 슬러지 처리 조회 요청 파라미터 */ +export interface SewageExcretaSlurgeRequestParams { + ctpvNm?: string; + sggNm?: string; + sewerBzmnCd?: string; + statsYr?: string; + fcltNm?: string; + mode?: string; +} + +/** + * ST00110001 조회 `data.slurgeProcessingFacStatusList[]` 한 행. + * `SlurgeProcessingFacStatusInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 `commonInfo.ctpvNm` 시도명). + */ +export interface SlurgeProcessingFacStatusItem { + admdstCd: string; + sggNm: string; + admdstNm: string; + fcltSn: string; + swtrmtFcltNm: string; + swtrmtFcltCd: string; + gnamWhol: number; + itslfQnt: number; + otsdInflwQnt: number; + transfTmplntNm: string; + transfPrcsQnt: number; + srpFncRt: number; + wholSrpPrcsQnt: number; + itslfTs1Whol: number; + itslfTs1RclmQnt: number; + itslfTs1IncnrQnt: number; + incnrPrcsWhol: number; + incnrPrcsLfvlm: number; + incnrPrcsPrdctRmtrlQnt: number; + incnrPrcsFmlyrQnt: number; + incnrPrcsFcltNm: string; + itslfTs1AltdQnt: number; + stbznPrcsWhol: number; + stbznPrcsLfvlm: number; + stbznPrcsFmlyrQnt: number; + stbznPrcsFcltNm: string; + itslfTs1DryQnt: number; + dryPrcsWhol: number; + dryPrcsLfvlm: number; + dryPrcsIncnrQnt: number; + dryPrcsFuelQnt: number; + dryPrcsPrdctRmtrlQnt: number; + dryPrcsFmlyrQnt: number; + dryPrcsCmpstgQnt: number; + dryPrcsFcltNm: string; + itslfTs1FuelQnt: number; + fuelPrcsWhol: number; + fuelPrcsIncnrQnt: number; + fuelPrcsFuelQnt: number; + fuelPrcsFcltNm: string; + itslfTs2CmpstgQnt: number; + cmpsPrcsFcltBzentyNm: string; + otsdCnsgnWhol: number; + otsdCnsgnRclmQnt: number; + otsdCnsgnIncnrQnt: number; + otsdCnsgnFuelPwplQnt: number; + otsdCnsgnPrdctRmtrlQnt: number; + otsdCnsgnFmlyrQnt: number; + otsdCnsgnCmpstgQnt: number; + otsdCnsgnFcltNm: string; + cparTs1Whol: number; + rmrk: string | null; + verificationResult?: SewageExcretaSlurgeVerificationResult; +} + +export interface SlurgeProcessingFacStatusResponseData { + statusInfo: SewerageStatusInfo; + commonInfo: SewageExcretaSlurgeCommonInfo; + slurgeProcessingFacStatusList: SlurgeProcessingFacStatusItem[]; +} + +export type SlurgeProcessingFacStatusApiResponse = + ApiResponse & { + message: string; + }; + +/** + * ST00110001 저장 — `saveList` / `deleteList` 항목. + * `sggNm`·계산필드·검증결과는 저장 요청에서 제외. + */ +export type SlurgeProcessingFacStatusSaveItem = Omit< + SlurgeProcessingFacStatusItem, + | "sggNm" + | "verificationResult" + | "gnamWhol" + | "wholSrpPrcsQnt" + | "itslfTs1Whol" + | "incnrPrcsWhol" + | "stbznPrcsWhol" + | "dryPrcsWhol" + | "fuelPrcsWhol" + | "otsdCnsgnWhol" +>; + +/** ST00110001 저장 API 요청 */ +export interface SlurgeProcessingFacStatusSaveRequest { + statsYr: string; + ctpvNm: string; + sggNm: string; + sewerBzmnCd: string; + userId: string; + superAdmin?: string; + noApplicable?: boolean; + saveList: SlurgeProcessingFacStatusSaveItem[]; + deleteList: SlurgeProcessingFacStatusSaveItem[]; +} + +export type SlurgeProcessingFacStatusSaveResponse = ApiResponse & { + message?: string; +}; + +export type SlurgeProcessingFacStatusSubmitItem = SlurgeProcessingFacStatusSaveItem; + +/** ST00110001 제출 API 요청 */ +export interface SlurgeProcessingFacStatusSubmitRequest { + statsYr: string; + ctpvNm: string; + sggNm: string; + sewerBzmnCd: string; + userId: string; + laePicNm: string; + laePicTelNo: string; + laeKbrdrDeptNm: string; + laeKbrdrJbgdNm: string; + laeIdfrNm: string; + laeIdfrTelNo: string; + laeIdfrDeptNm: string; + laeIdfrJbgdNm: string; + superAdmin?: string; + noApplicable?: boolean; + submitList: SlurgeProcessingFacStatusSubmitItem[]; + deleteList: SlurgeProcessingFacStatusSubmitItem[]; +} + +export interface SlurgeProcessingFacStatusSubmitResponse { + status: string; + message?: string; + data?: { + finalStatus: string; + nvpassed: boolean; + }; +} + +/** + * ST00110002 조회 `data.digestorOperatingStatusList[]` 한 행. + * `DigestorOperatingStatusInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 `commonInfo.ctpvNm` 시도명). + */ +export interface DigestorOperatingStatusItem { + admdstCd: string; + sggNm: string; + admdstNm: string; + fcltSn: string; + swtrmtFcltCd: string; + swtrmtFcltNm: string; + digstnEnYn: string; + digstnCpct: number; + actlDrvnCpct: number; + digstnGasInputQnt: number; + digstnGasInputSldDnst: number; + digstnTnkOprtngDayCnt: number; + ocrnGasQnt: number; + digstnGasUtztnWholPrst: number; + digstnGasUtztnNtslQnt: number; + digstnGasUtztnEtgtQnt: number; + digstnGasUtztnItslfQnt: number; + digstnGasUtztnEtcQnt: number; + surplsGasQnt: number; + rmrk: string | null; + verificationResult?: SewageExcretaSlurgeVerificationResult; +} + +export interface DigestorOperatingStatusResponseData { + statusInfo: SewerageStatusInfo; + commonInfo: SewageExcretaSlurgeCommonInfo; + digestorOperatingStatusList: DigestorOperatingStatusItem[]; +} + +export type DigestorOperatingStatusApiResponse = + ApiResponse & { + message: string; + }; + +/** + * ST00110002 저장 — `saveList` 항목. + * `sggNm`·계산필드·검증결과는 저장 요청에서 제외. + */ +export type DigestorOperatingStatusSaveItem = Omit< + DigestorOperatingStatusItem, + "sggNm" | "verificationResult" | "digstnGasUtztnWholPrst" +>; + +/** ST00110002 저장 API 요청 */ +export interface DigestorOperatingStatusSaveRequest { + statsYr: string; + ctpvNm: string; + sggNm: string; + sewerBzmnCd: string; + userId: string; + superAdmin?: string; + noApplicable?: boolean; + saveList: DigestorOperatingStatusSaveItem[]; + deleteList?: DigestorOperatingStatusSaveItem[]; +} + +export type DigestorOperatingStatusSaveResponse = ApiResponse & { + message?: string; +}; + +export type DigestorOperatingStatusSubmitItem = DigestorOperatingStatusSaveItem; + +/** ST00110002 제출 API 요청 */ +export interface DigestorOperatingStatusSubmitRequest { + statsYr: string; + ctpvNm: string; + sggNm: string; + sewerBzmnCd: string; + userId: string; + laePicNm: string; + laePicTelNo: string; + laeKbrdrDeptNm: string; + laeKbrdrJbgdNm: string; + laeIdfrNm: string; + laeIdfrTelNo: string; + laeIdfrDeptNm: string; + laeIdfrJbgdNm: string; + superAdmin?: string; + noApplicable?: boolean; + submitList: DigestorOperatingStatusSubmitItem[]; +} + +export interface DigestorOperatingStatusSubmitResponse { + status: string; + message?: string; + data?: { + finalStatus: string; + nvpassed: boolean; + }; +} + +/** + * ST00110003 조회 `data.sewageSlurgeProcessingFacStatusList[]` 한 행. + * `SewageSlurgeProcessingFacStatusInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 `commonInfo.ctpvNm` 시도명). + */ +export interface SewageSlurgeProcessingFacStatusItem { + admdstCd: string; + sggNm: string; + admdstNm: string; + fcltSn: string; + fcltNm: string; + prcsMthExpln: string; + fcltCpct: number; + anlPrcsQnt: number; + dayAvgOprtngHr: number; + anlOprtngDayCnt: number; + oprtngRt: number; + rmrk: string | null; + fcltPstnCn: string; + verificationResult?: SewageExcretaSlurgeVerificationResult; +} + +export interface SewageSlurgeProcessingFacStatusResponseData { + statusInfo: SewerageStatusInfo; + commonInfo: SewageExcretaSlurgeCommonInfo; + sewageSlurgeProcessingFacStatusList: SewageSlurgeProcessingFacStatusItem[]; +} + +export type SewageSlurgeProcessingFacStatusApiResponse = + ApiResponse & { + message: string; + }; + +/** + * ST00110003 저장 — `saveList` / `deleteList` 항목. + * `sggNm`·계산필드·검증결과·`fcltPstnCn`은 저장 요청에서 제외. + */ +export type SewageSlurgeProcessingFacStatusSaveItem = Omit< + SewageSlurgeProcessingFacStatusItem, + "sggNm" | "verificationResult" | "oprtngRt" | "fcltPstnCn" +>; + +/** ST00110003 저장 API 요청 */ +export interface SewageSlurgeProcessingFacStatusSaveRequest { + statsYr: string; + ctpvNm: string; + sggNm: string; + sewerBzmnCd: string; + userId: string; + superAdmin?: string; + noApplicable?: boolean; + saveList: SewageSlurgeProcessingFacStatusSaveItem[]; + deleteList: SewageSlurgeProcessingFacStatusSaveItem[]; +} + +export type SewageSlurgeProcessingFacStatusSaveResponse = ApiResponse & { + message?: string; +}; + +export type SewageSlurgeProcessingFacStatusSubmitItem = + SewageSlurgeProcessingFacStatusSaveItem; + +/** ST00110003 제출 API 요청 */ +export interface SewageSlurgeProcessingFacStatusSubmitRequest { + statsYr: string; + ctpvNm: string; + sggNm: string; + sewerBzmnCd: string; + userId: string; + laePicNm: string; + laePicTelNo: string; + laeKbrdrDeptNm: string; + laeKbrdrJbgdNm: string; + laeIdfrNm: string; + laeIdfrTelNo: string; + laeIdfrDeptNm: string; + laeIdfrJbgdNm: string; + superAdmin?: string; + noApplicable?: boolean; + submitList: SewageSlurgeProcessingFacStatusSubmitItem[]; + deleteList: SewageSlurgeProcessingFacStatusSubmitItem[]; +} + +export interface SewageSlurgeProcessingFacStatusSubmitResponse { + status: string; + message?: string; + data?: { + finalStatus: string; + nvpassed: boolean; + }; +} + +/** + * ST00110004 조회 `data.excretaSlurgeProcessingStatusList[]` 한 행. + * `ExcretaSlurgeProcessingStatusInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 `commonInfo.ctpvNm` 시도명). + */ +export interface ExcretaSlurgeProcessingStatusItem { + admdstCd: string; + sggNm: string; + admdstNm: string; + fcltSn: string; + fcltNm: string; + fcltPstnCn: string; + gnam: number; + wholDspsQnt: number; + rcycQnt: number; + olandLfvlm: number; + incnrQnt: number; + oceanInputQnt: number; + fuelQnt: number; + etcQnt: number; + nondspsQnt: number; + swtrmtFcltNm: string; + rmrk: string | null; + verificationResult?: SewageExcretaSlurgeVerificationResult; +} + +export interface ExcretaSlurgeProcessingStatusResponseData { + statusInfo: SewerageStatusInfo; + commonInfo: SewageExcretaSlurgeCommonInfo; + excretaSlurgeProcessingStatusList: ExcretaSlurgeProcessingStatusItem[]; +} + +export type ExcretaSlurgeProcessingStatusApiResponse = + ApiResponse & { + message: string; + }; + +/** + * ST00110004 저장 — `saveList` 항목. + * `sggNm`·계산필드(`gnam`, `wholDspsQnt`)·검증결과는 저장 요청에서 제외. + */ +export type ExcretaSlurgeProcessingStatusSaveItem = Omit< + ExcretaSlurgeProcessingStatusItem, + "sggNm" | "verificationResult" | "gnam" | "wholDspsQnt" +>; + +/** ST00110004 저장 API 요청 */ +export interface ExcretaSlurgeProcessingStatusSaveRequest { + statsYr: string; + ctpvNm: string; + sggNm: string; + sewerBzmnCd: string; + userId: string; + superAdmin?: string; + noApplicable?: boolean; + saveList: ExcretaSlurgeProcessingStatusSaveItem[]; +} + +export type ExcretaSlurgeProcessingStatusSaveResponse = ApiResponse & { + message?: string; +}; + +export type ExcretaSlurgeProcessingStatusSubmitItem = ExcretaSlurgeProcessingStatusSaveItem; + +/** ST00110004 제출 API 요청 */ +export interface ExcretaSlurgeProcessingStatusSubmitRequest { + statsYr: string; + ctpvNm: string; + sggNm: string; + sewerBzmnCd: string; + userId: string; + laePicNm: string; + laePicTelNo: string; + laeKbrdrDeptNm: string; + laeKbrdrJbgdNm: string; + laeIdfrNm: string; + laeIdfrTelNo: string; + laeIdfrDeptNm: string; + laeIdfrJbgdNm: string; + superAdmin?: string; + noApplicable?: boolean; + submitList: ExcretaSlurgeProcessingStatusSubmitItem[]; +} + +export interface ExcretaSlurgeProcessingStatusSubmitResponse { + status: string; + message?: string; + data?: { + finalStatus: string; + nvpassed: boolean; + }; +} + +/** `public-sewage-facilities` 조회 요청 */ +export interface PublicSewageFacilitiesListRequest { + statsYr: string; + sewerBzmnCd: string; + ctpvNm: string; + sggNm: string; +} + +/** `public-sewage-facilities` 조회 `data[]` 한 행 */ +export interface PublicSewageFacilityItem { + admdstCd: string; + admdstNm: string; + swtrmtFcltCd: string; + swtrmtFcltNm: string; + fcltSn: string; +} + +export type PublicSewageFacilitiesListApiResponse = ApiResponse & { + message: string; +}; + +export interface StatisticsInputSewageExcretaSlurgeService { + /** 찌꺼기(슬러지)처리시설현황 조회 — ST00110001 */ + getSlurgeProcessingFacStatusList( + params: SewageExcretaSlurgeRequestParams, + ): Promise; + /** 찌꺼기(슬러지)처리시설현황 저장 — ST00110001 */ + saveSlurgeProcessingFacStatus( + request: SlurgeProcessingFacStatusSaveRequest, + ): Promise; + /** 찌꺼기(슬러지)처리시설현황 제출 — ST00110001 */ + submitSlurgeProcessingFacStatus( + request: SlurgeProcessingFacStatusSubmitRequest, + ): Promise; + /** 소화조 운영현황 조회 — ST00110002 */ + getDigestorOperatingStatusList( + params: SewageExcretaSlurgeRequestParams, + ): Promise; + /** 소화조 운영현황 저장 — ST00110002 */ + saveDigestorOperatingStatus( + request: DigestorOperatingStatusSaveRequest, + ): Promise; + /** 소화조 운영현황 제출 — ST00110002 */ + submitDigestorOperatingStatus( + request: DigestorOperatingStatusSubmitRequest, + ): Promise; + /** 하수찌꺼기 처리시설 운영현황 조회 — ST00110003 */ + getSewageSlurgeProcessingFacStatusList( + params: SewageExcretaSlurgeRequestParams, + ): Promise; + /** 하수찌꺼기 처리시설 운영현황 저장 — ST00110003 */ + saveSewageSlurgeProcessingFacStatus( + request: SewageSlurgeProcessingFacStatusSaveRequest, + ): Promise; + /** 하수찌꺼기 처리시설 운영현황 제출 — ST00110003 */ + submitSewageSlurgeProcessingFacStatus( + request: SewageSlurgeProcessingFacStatusSubmitRequest, + ): Promise; + /** 분뇨찌꺼기(슬러지)처리현황 조회 — ST00110004 */ + getExcretaSlurgeProcessingStatusList( + params: SewageExcretaSlurgeRequestParams, + ): Promise; + /** 분뇨찌꺼기(슬러지)처리현황 저장 — ST00110004 */ + saveExcretaSlurgeProcessingStatus( + request: ExcretaSlurgeProcessingStatusSaveRequest, + ): Promise; + /** 분뇨찌꺼기(슬러지)처리현황 제출 — ST00110004 */ + submitExcretaSlurgeProcessingStatus( + request: ExcretaSlurgeProcessingStatusSubmitRequest, + ): Promise; + /** 공공하수처리시설 목록 조회 */ + getPublicSewageFacilitiesList( + params: PublicSewageFacilitiesListRequest, + ): Promise; +}