From 60c515a65428624acc1cec084350d957a1cdeba0 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Thu, 12 Feb 2026 10:50:07 +0900 Subject: [PATCH] =?UTF-8?q?st:=20fix=20&=20refactor:=20=ED=95=98=EC=88=98?= =?UTF-8?q?=EB=8F=84=20=EB=B3=B4=EA=B8=89=EB=A5=A0=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20response=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20TadaGrid=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20Tab=20stat?= =?UTF-8?q?e=20=EB=B6=80=EB=AA=A8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20prop=EB=A1=9C=20=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95,=20grid=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EA=B8=B0=20=EC=9C=84=ED=95=9C=20ref=20prop=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(TabDataGrid.tsx=EB=8A=94=20=EC=95=84?= =?UTF-8?q?=EC=A7=81=20=EB=AF=B8=EA=B5=AC=ED=98=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/module/DataGrid.tsx | 39 +++- apps/st/src/components/module/TabDataGrid.tsx | 18 +- .../input/SeweragePenetrationRateInp.tsx | 133 ++++++++++- .../statistics/review/SewagePipeRvw.tsx | 51 +++-- .../review/SeweragePenetrationRateRvw.tsx | 2 +- .../statistics/review/StatsInputStatusRvw.tsx | 2 +- .../ManagementVerificationRule.tsx | 3 +- apps/st/src/components/type/DataGrid.type.ts | 6 + .../input/SeweragePenetrationRateInpColumn.ts | 18 +- .../util/column/review/SewagePipeRvwColumn.ts | 216 +++++++++--------- .../api/statistics-input-service.ts | 11 + .../api/statistics-management-service.ts | 2 +- .../model/statistics-input-interface.ts | 71 +++++- 13 files changed, 413 insertions(+), 159 deletions(-) diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index b762ed5cb..4e423571d 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -164,7 +164,7 @@ function createOnSortingHandler( const mergeCellCols = columns.filter((c) => c.mergeCells === true && c.fieldName); const mergeCellCol = mergeCellCols.find((c) => c.fieldName === 'gugun') || mergeCellCols[0]; const groupField = mergeCellCol?.fieldName || null; - const orderByField = 'rgnNm'; // 고정값 또는 설정 가능하게 할 수 있음 + const orderByField = 'displayYear'; // 고정값 또는 설정 가능하게 할 수 있음 const hasMergeCells = mergeCellCols.length > 0; return function onSorting(g: unknown, fields: unknown[], _directions: unknown[]) { @@ -289,7 +289,7 @@ function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueT }); } -const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport = true, fixedColumns, showHeaderCheckbox = false, rowData, setRowData }: DataGridProps) => { +const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport = true, fixedColumns, showHeaderCheckbox = false, rowData, setRowData, getGridDataRef }: DataGridProps) => { const { columns, columnLayout } = columnInfo; const gridRef = useRef(null); const fileInputRef = useRef(null); @@ -748,8 +748,28 @@ const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport }); }, 200); - }, [columns, columnLayout, fixedColumns, rowData, showHeaderCheckbox]); + if (getGridDataRef) { + const fieldNames = columns.map((c) => c.fieldName).filter(Boolean) as string[]; + getGridDataRef.current = () => { + const gv = gridRef.current?.gridView as { getDataSource?: () => { getRowCount(): number; getValue(r: number, f: string): unknown } } | undefined; + if (!gv?.getDataSource) return []; + const ds = gv.getDataSource(); + if (!ds) return []; + const cnt = ds.getRowCount(); + const rows: Record[] = []; + for (let i = 0; i < cnt; i++) { + const r: Record = {}; + for (const f of fieldNames) r[f] = ds.getValue(i, f) as string | number; + rows.push(r); + } + return rows; + }; + } + return () => { + if (getGridDataRef) getGridDataRef.current = null; + }; + }, [columns, columnLayout, fixedColumns, rowData, showHeaderCheckbox, getGridDataRef]); return (
@@ -782,13 +802,14 @@ const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport rows={rowData} onInitialized={(grid) => { (grid as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef); - const dp = gridRef.current?.dataProvider as { setFields?: (fields: unknown[]) => void; setRows?: (rows: Record[]) => void } | undefined; - if (dp?.setFields) { - dp.setFields(buildFieldsFromColumns(columns)); + const dataProvider = gridRef.current?.dataProvider as unknown as + | { setFields?(f: unknown[]): void; setRows?(r: Record[]): void; getJsonRow?(r: number): Record } + | undefined; + if (dataProvider?.setFields) { + dataProvider.setFields(buildFieldsFromColumns(columns)); } - // setFields 호출 직후 행 데이터 적용 (realgrid-react의 setRows 타이밍 이슈 방지) - if (dp?.setRows && rowData?.length) { - dp.setRows(rowData); + if (dataProvider?.setRows && rowData?.length) { + dataProvider.setRows(rowData); } }} > diff --git a/apps/st/src/components/module/TabDataGrid.tsx b/apps/st/src/components/module/TabDataGrid.tsx index 532a342bd..a7e0e3383 100644 --- a/apps/st/src/components/module/TabDataGrid.tsx +++ b/apps/st/src/components/module/TabDataGrid.tsx @@ -162,7 +162,7 @@ function createOnSortingHandler( const mergeCellCols = columns.filter((c) => c.mergeCells === true && c.fieldName); const mergeCellCol = mergeCellCols.find((c) => c.fieldName === 'gugun') || mergeCellCols[0]; const groupField = mergeCellCol?.fieldName || null; - const orderByField = 'rgnNm'; // 고정값 또는 설정 가능하게 할 수 있음 + const orderByField = 'displayYear'; // 고정값 또는 설정 가능하게 할 수 있음 const hasMergeCells = mergeCellCols.length > 0; return function onSorting(g: unknown, fields: unknown[], _directions: unknown[]) { @@ -306,7 +306,7 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string }; } -const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, rows = [], showHeaderCheckbox = false, rowDataMap, setRowDataMap }: TabDataGridProps) => { +const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, rows = [], showHeaderCheckbox = false, rowDataMap, setRowDataMap, activeTab: activeTabProp, onActiveTabChange }: TabDataGridProps) => { // 각 탭별로 그리드 ref와 데이터를 관리 const gridRefs = useRef>(new Map()); const fileInputRefs = useRef>(new Map()); @@ -316,11 +316,14 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r const { setMessage: alert } = useAlertStore((state) => state); const activeTabId = useTabStore((state) => state.activeTabId); const menuList = useTabStore((state) => state.menuList); - const [activeTab, setActiveTab] = useState(() => { - // 초기 활성 탭을 첫 번째 tabKey로 설정 + + const [internalActiveTab, setInternalActiveTab] = useState(() => { const firstTab = columnInfo[0]; return firstTab?.tabKey || ''; }); + const isControlled = activeTabProp !== undefined; + const activeTab = isControlled ? activeTabProp : internalActiveTab; + const setActiveTab = isControlled ? (onActiveTabChange ?? (() => {})) : setInternalActiveTab; const exportExcel = (tabKey: string) => { const currentRowData = rowDataMap.get(tabKey) || []; @@ -803,10 +806,15 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r const columns = currentInfo.columns; - const dp = el.dataProvider as { setFields?: (fields: unknown[]) => void } | undefined; + const dp = el.dataProvider as { setFields?: (fields: unknown[]) => void; setRows?: (rows: Record[]) => void } | undefined; if (dp?.setFields) { dp.setFields(buildFieldsFromColumns(columns)); } + // setFields 호출 직후 행 데이터 적용 (realgrid-react의 setRows 타이밍 이슈 방지) + const tabRowData = rowDataMap.get(tabKey) || []; + if (dp?.setRows && tabRowData.length) { + dp.setRows(tabRowData); + } gridView.setDisplayOptions({ fitStyle: GridFitStyle.EVEN diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 7644ab1e3..70d4f10ae 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from "react"; +import { useState, useMemo, useRef } from "react"; import SearchForm from "@/components/module/SearchForm"; // import { DataGrid } from "@rsone/components"; @@ -8,6 +8,7 @@ import SaveButton from "@/components/button/SaveButton"; import LastYearDataButton from "@/components/button/LastYearDataButton"; import SubmitExceptionButton from "@/components/button/SubmitExceptionButton"; import { LoadingWithAnimation } from "@common/components"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAlertStore } from "@/shared/lib"; import { statisticsInputAPi } from "@/entities/statistics/api/statistics-input-service"; @@ -15,9 +16,13 @@ import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SewerageSupplyRateItem, SewerageSupplyRateRequestParams, + SewerageSupplyRateSaveItem, } from "@/entities/statistics/model/statistics-input-interface"; import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { getFooterSummary } from "@/components/util/grid/footerSummary"; +import { getUserInfo } from "../../../../../../packages/common/components/src/api"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; function getSewerageSupplyRateParams( values: SearchFormValues, @@ -28,7 +33,6 @@ function getSewerageSupplyRateParams( gugun: values.gugun === 'ALL' ? '' : values.gugun, bsnmCd: values.bsnmCd, yr: overrides?.yr ?? values.year, - demoYr: overrides?.demoYr ?? values.year, ...(overrides?.mode && { mode: overrides.mode }), }; } @@ -41,22 +45,38 @@ const SeweragePenetrationRateInp = () => { const [searchValues, setSearchValues] = useState(null); const [isEnterable, setIsEnterable] = useState(true); const [lastSearchedYear, setLastSearchedYear] = useState(null); + const { confirm } = useConfirm(); + const [footerFromApi, setFooterFromApi] = useState<{ totalPopl?: number; poplSm?: number; frgnrPoplSm?: number } | null>(null); + const getGridDataRef = useRef<(() => Record[]) | null>(null); + + const footerFieldNames = ['totalPopl', 'poplSm', 'frgnrPoplSm'] as const; + const footerWithApiDefault = (fieldName: (typeof footerFieldNames)[number], numberFormat: string) => ({ + expression: 'sum' as const, + numberFormat, + valueCallback: (grid: Parameters[0], column: Parameters[1]) => + (footerFromApi && footerFromApi[fieldName] != null ? footerFromApi[fieldName] : null) ?? getFooterSummary(grid, column), + }); // 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요. const columnInfo = useMemo(() => { const { columns, columnLayout } = SeweragePenetrationRateInpColumn; + const base = isEnterable ? columns : columns.map((col) => ({ ...col, editable: false as const })); return { columnLayout, - columns: isEnterable - ? columns - : columns.map((col) => ({ ...col, editable: false as const })), + columns: base.map((col) => { + if (col.fieldName === 'totalPopl') return { ...col, footer: footerWithApiDefault('totalPopl', '#,##0') }; + if (col.fieldName === 'poplSm') return { ...col, footer: footerWithApiDefault('poplSm', '#,##0') }; + if (col.fieldName === 'frgnrPoplSm') return { ...col, footer: footerWithApiDefault('frgnrPoplSm', '#,##0') }; + return col; + }), }; - }, [isEnterable]); + }, [isEnterable, footerFromApi]); const handleReset = () => { setRowData([]); setLastSearchedYear(null); + setFooterFromApi(null); }; const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams) => { @@ -64,18 +84,30 @@ const SeweragePenetrationRateInp = () => { setIsLoading(true); const response = await statisticsInputAPi.getSewerageSupplyRateList(params); if (response.status === 'success' && response.data) { + const commonInfo = response.data.commonInfo; const convertedData: SewerageSupplyRateItem[] = response.data.sewerageSupplyRateList.map((d) => ({ ...d, + sido: commonInfo.sido, speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr, })); - setRowData(toGridRows(convertedData)); + + setFooterFromApi( + commonInfo + ? { + totalPopl: commonInfo.bsnmTotalPopl, + poplSm: commonInfo.bsnmPoplSm, + frgnrPoplSm: commonInfo.bsnmFrgnrPoplSm, + } + : null + ); } else { alert(response.message); } } catch { alert('데이터 조회 중 오류가 발생했습니다.'); setRowData([]); + setFooterFromApi(null); } finally { setIsLoading(false); } @@ -126,11 +158,91 @@ const SeweragePenetrationRateInp = () => { // 제출 로직 }; - const saveFunc = () => { - // 저장 로직 + const saveFunc = async () => { + try { + if (await confirm('저장 하시겠습니까?')) { + setIsLoading(true); + + const userInfo = await getUserInfo(); + const currentData = getGridDataRef.current?.(); + + if (!currentData || currentData.length === 0) { + alert('저장할 데이터가 없습니다.'); + return; + } + + // 컬럼 정의에서 dataFormat 추출 (소수점 자리수 맞추기 위해) + const columnFormatMap = new Map(); + SeweragePenetrationRateInpColumn.columns.forEach((col) => { + if (col.fieldName && col.dataFormat) { + columnFormatMap.set(col.fieldName, col.dataFormat); + } + }); + + // 숫자 포맷팅 함수 (dataFormat에 따라 소수점 자리수 맞춤) + const formatNumber = (value: string | number | null | undefined, fieldName: string): number => { + if (value === null || value === undefined || value === '') return 0; + const num = typeof value === 'string' ? parseFloat(value.replace(/,/g, '')) : value; + if (isNaN(num)) return 0; + + const format = columnFormatMap.get(fieldName); + if (!format) return num; + + // #,##0.000 → 소수점 3자리, #,##0.0 → 소수점 1자리, #,##0 → 정수 + const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0; + return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num); + }; + + // SaveItem 타입에 정의된 필드만 추출 + const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({ + bsnmCd: searchValues?.bsnmCd ?? '', + sido: d.sido as string, + gugun: (d.gugun as string) || null, + yr: searchValues?.year ?? '', + adstCd: d.adstCd as string, + adstDong: d.adstDong as string, + bstrm: d.bstrm as string, + speclCntrplnAr: d.speclCntrplnAr === '해당없음' ? null : (d.speclCntrplnAr as string | null), + pblswrgPrmTrt: formatNumber(d.pblswrgPrmTrt, 'pblswrgPrmTrt'), + pblswrgScdTrt: formatNumber(d.pblswrgScdTrt, 'pblswrgScdTrt'), + pblswrgTertTrt: formatNumber(d.pblswrgTertTrt, 'pblswrgTertTrt'), + wstrPrmTrt: formatNumber(d.wstrPrmTrt, 'wstrPrmTrt'), + wstrScdTrt: formatNumber(d.wstrScdTrt, 'wstrScdTrt'), + wstrTertTrt: formatNumber(d.wstrTertTrt, 'wstrTertTrt'), + notConectPopltn: formatNumber(d.notConectPopltn, 'notConectPopltn'), + swgeProcZoneInnerAr: formatNumber(d.swgeProcZoneInnerAr, 'swgeProcZoneInnerAr'), + townUnprocPopltn: formatNumber(d.townUnprocPopltn, 'townUnprocPopltn'), + townSwttPopltn: formatNumber(d.townSwttPopltn, 'townSwttPopltn'), + townWrrtnPopltn: formatNumber(d.townWrrtnPopltn, 'townWrrtnPopltn'), + swgeProcZoneOuterAr: formatNumber(d.swgeProcZoneOuterAr, 'swgeProcZoneOuterAr'), + rmk: (d.rmk as string) ?? '', + popltnSm: formatNumber(d.popltnSm, 'popltnSm'), + arSm: formatNumber(d.arSm, 'arSm'), + swgeProcZoneInnerSm: formatNumber(d.swgeProcZoneInnerSm, 'swgeProcZoneInnerSm'), + pblswrgPopltnSm: formatNumber(d.pblswrgPopltnSm, 'pblswrgPopltnSm'), + wstrPopltnSm: formatNumber(d.wstrPopltnSm, 'wstrPopltnSm'), + swgeProcZoneOuterSm: formatNumber(d.swgeProcZoneOuterSm, 'swgeProcZoneOuterSm'), + pblswrgPopltnSuprt: formatNumber(d.pblswrgPopltnSuprt, 'pblswrgPopltnSuprt'), + adtrtPopltnSuprt: formatNumber(d.adtrtPopltnSuprt, 'adtrtPopltnSuprt'), + swrgInstlrt: formatNumber(d.swrgInstlrt, 'swrgInstlrt'), + userId: userInfo.userName, + })); + + const response = await statisticsInputAPi.saveSewerageSupplyRate(convertedData); + console.log(convertedData) + console.log(response); + } else { + // 저장 취소 + } + } catch { + alert('데이터 저장 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } }; return ( + <>
{rowData.length > 0 && @@ -159,8 +271,11 @@ const SeweragePenetrationRateInp = () => { fixedColumns={1} rowData={rowData} setRowData={setRowData} + getGridDataRef={getGridDataRef} />
+ + ); }; diff --git a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx index ccfc62355..484c0d574 100644 --- a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from "react"; +import { useState, useMemo, act } from "react"; import TabDataGrid from "@/components/module/TabDataGrid"; import SearchForm from "@/components/module/SearchForm"; import SaveButton from "@/components/button/SaveButton"; @@ -18,6 +18,7 @@ const SewagePipeRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); const [rowDataMap, setRowDataMap] = useState>(new Map()); const [isLoading, setIsLoading] = useState(false); + const [activeTab, setActiveTab] = useState('a'); const columnInfo = useMemo(() => { return SewagePipeColumnInfoListRvw.map((tab: any) => ({ @@ -40,23 +41,29 @@ const SewagePipeRvw = () => { try { setIsLoading(true); - const response = await statisticsManagementApi.getSewagePipeReviewList({ + + const searchParam = { sido: values.sido, gugun: values.gugun === 'ALL' ? '' : values.gugun, bsnmCd: values.bsnmCd, yr: values.year, - }); + }; - if (response.status === 'success' && response.data) { + let response; + + if (activeTab === 'a') { + response = await statisticsManagementApi.getSewagePipeReviewList(searchParam); + } + + if (response?.status === 'success' && response.data) { const rawList = response.data; const newRowDataMap = new Map(); - - newRowDataMap.set('a', toGridRows(rawList)); - + + newRowDataMap.set(activeTab, toGridRows(rawList)); + setRowDataMap(newRowDataMap); } else { - handleReset(); - alert(response.message || "데이터가 없습니다."); + alert(response?.message || "데이터가 없습니다."); } } catch (error) { console.error("조회 에러:", error); @@ -68,30 +75,32 @@ const SewagePipeRvw = () => { }; return ( -
+
- +
- {}} /> - {}} /> - {}} /> - {}} /> + { }} /> + { }} /> + { }} /> + { }} />
{isLoading && ( -
+
)} -
); diff --git a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx index bf6ef9911..8ad001c4c 100644 --- a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx +++ b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx @@ -93,7 +93,7 @@ const SeweragePenetrationRateRvw = () => {
{isLoading && ( -
+
diff --git a/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx b/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx index 7a7bb4788..2c11f7abc 100644 --- a/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx +++ b/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx @@ -91,7 +91,7 @@ const StatsInputStatusRvw = () => {
{isLoading && ( -
+
diff --git a/apps/st/src/components/statistics/verification/ManagementVerificationRule.tsx b/apps/st/src/components/statistics/verification/ManagementVerificationRule.tsx index 1d5dfd169..708e2c02e 100644 --- a/apps/st/src/components/statistics/verification/ManagementVerificationRule.tsx +++ b/apps/st/src/components/statistics/verification/ManagementVerificationRule.tsx @@ -1,7 +1,8 @@ "use client"; import { useState } from "react"; -import { DataGrid } from "@rsone/components"; +// import { DataGrid } from "@rsone/components"; +import DataGrid from "@/components/module/DataGrid"; import SearchForm from "@/components/module/SearchForm"; import { Button, diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index 4d31facbe..8079148d6 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -150,6 +150,8 @@ export type DataGridProps = { showHeaderCheckbox?: boolean; // 헤더 체크박스 표시 여부 (기본값: true) rowData: Record[]; setRowData: React.Dispatch[]>>; + /** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */ + getGridDataRef?: { current: (() => Record[]) | null }; }; export type TabDataGridProps = { @@ -162,6 +164,10 @@ export type TabDataGridProps = { rowDataMap: Map[]>; setRowDataMap: React.Dispatch[]>>>; isSplitGrid?: boolean; // 5:5 분리형 그리드 유무 + /** 활성 탭 키 (제어 모드). 부모에서 넘기면 해당 탭이 선택됨 */ + activeTab?: string; + /** 활성 탭 변경 시 호출 (제어 모드). activeTab과 함께 넘기면 부모가 탭 상태를 가짐 */ + onActiveTabChange?: (tabKey: string) => void; }; export type SplitDataGridProps = { diff --git a/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts b/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts index 39d3c2234..159726976 100644 --- a/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts +++ b/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts @@ -1,4 +1,4 @@ -import { footerInt, footerDecimal1, footerDecimal3, footerAvgDecimal } from "@/components/util/grid/footerSummary"; +import { footerInt, footerDecimal3, footerAvgDecimal } from "@/components/util/grid/footerSummary"; import { ValueType } from "realgrid"; /** * 하수도 통계 관리 > 01. 하수도 보급률 @@ -32,7 +32,17 @@ export const SeweragePenetrationRateInpColumn = { { name: 'area', fieldName: 'area', header: { text: '행정참고자료면적(㎢)' }, footer: footerDecimal3, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, // 총인구 - { name: 'popltnSm', fieldName: 'popltnSm', header: { text: '총인구(명)' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { + name: 'popltnSm', + fieldName: 'popltnSm', + header: { text: '총인구(명)' }, + footer: footerInt, + editable: false, + valueType: ValueType.NUMBER, + dataFormat: '#,##0', + computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], type: 'sum' as const }, + styleName: 'rg-cell-total', + }, // 총면적 { @@ -55,7 +65,7 @@ export const SeweragePenetrationRateInpColumn = { editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], type: 'sum' as const }, + computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], type: 'sum' as const }, styleName: 'rg-cell-total', }, @@ -170,6 +180,7 @@ export const SeweragePenetrationRateInpColumn = { { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, { name: 'piplSupprt', fieldName: 'piplSupprt', header: { text: '하수관로보급률(%)' }, visible: false }, + { name: 'adstCd', fieldName: 'adstCd', header: { text: '행정코드' }, visible: false }, ], columnLayout: [ @@ -240,5 +251,6 @@ export const SeweragePenetrationRateInpColumn = { 'swrgInstlrt', 'rmk', 'piplSupprt', + 'adstCd', ] }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts index 732752343..28c2b4ad1 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -18,142 +18,144 @@ const rawList = [ mergeCells: true, editable: false, sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; + return '합계'; } - const divisionValues = Array.from(uniqueValues); - if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; - return '합계'; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; + return '합계'; } - const divisionValues = Array.from(uniqueValues); - if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; - return '합계'; } - } - ] - }, + ] + }, { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, editable: false }, - { - name: 'displayYear', - fieldName: 'displayYear', - header: { text: '구분' }, - editable: false, - sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; } - } - ] - }, + ] + }, /* 하수정비 기본계획 */ { name: 'targetYr', fieldName: 'targetYr', header: { text: '하수정보기본계획목표년도' } }, { name: 'piplImprmnPlanExtn', fieldName: 'piplImprmnPlanExtn', header: { text: '총관로정비\n계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'piplPlanExtn', fieldName: 'piplPlanExtn', header: { text: '총계획연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'piplFcltyExtn', fieldName: 'piplFcltyExtn', header: { text: '총시설연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'piplSupprt', fieldName: 'piplSupprt', header: { text: '하수관로\n보급률(%)' }, footer: footerAvgDecimalArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'piplPlanExtn', fieldName: 'piplPlanExtn', header: { text: '총계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'piplFcltyExtn', fieldName: 'piplFcltyExtn', header: { text: '총시설연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'piplSupprt', fieldName: 'piplSupprt', header: { text: '하수관로\n보급률(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, /* 합류식(m) */ - { name: 'cmbnPlanAr', fieldName: 'cmbnPlanAr', header: { text: '관로정비\n계획면적(㎢)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'cmbnImprmnPlanExtn', fieldName: 'cmbnImprmnPlanExtn', header: { text: '관로정비\n계획연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'cmbnPlanExtn', fieldName: 'cmbnPlanExtn', header: { text: '계획연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'cmbnFcltyExtn', fieldName: 'cmbnFcltyExtn', header: { text: '시설연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'cmbnRctglExtn', fieldName: 'cmbnRctglExtn', header: { text: '사각형(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'cmbnCicleExtn', fieldName: 'cmbnCicleExtn', header: { text: '원형(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'cmbnOpswrExtn', fieldName: 'cmbnOpswrExtn', header: { text: '개거(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'cmbnSdgttrExtn', fieldName: 'cmbnSdgttrExtn', header: { text: '측구(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnPlanAr', fieldName: 'cmbnPlanAr', header: { text: '관로정비\n계획면적(㎢)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'cmbnImprmnPlanExtn', fieldName: 'cmbnImprmnPlanExtn', header: { text: '관로정비\n계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnPlanExtn', fieldName: 'cmbnPlanExtn', header: { text: '계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnFcltyExtn', fieldName: 'cmbnFcltyExtn', header: { text: '시설연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnRctglExtn', fieldName: 'cmbnRctglExtn', header: { text: '사각형(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnCicleExtn', fieldName: 'cmbnCicleExtn', header: { text: '원형(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnOpswrExtn', fieldName: 'cmbnOpswrExtn', header: { text: '개거(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnSdgttrExtn', fieldName: 'cmbnSdgttrExtn', header: { text: '측구(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, /* 분류식 - 오수관로 */ - { name: 'pltwtrPlanAr', fieldName: 'pltwtrPlanAr', header: { text: '관로정비\n계획면적(㎢)' }, footer: footerDecimal3ArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'pltwtrImprmnPlanExtn', fieldName: 'pltwtrImprmnPlanExtn', header: { text: '관로정비\n계획연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'pltwtrPlanExtn', fieldName: 'pltwtrPlanExtn', header: { text: '계획연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'pltwtrFlctyExtn', fieldName: 'pltwtrFlctyExtn', header: { text: '시설연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'pltwtrSqreExtn', fieldName: 'pltwtrSqreExtn', header: { text: '사각형(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'pltwtrCiclExtn', fieldName: 'pltwtrCiclExtn', header: { text: '원형(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'pltwtrPlanAr', fieldName: 'pltwtrPlanAr', header: { text: '관로정비\n계획면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'pltwtrImprmnPlanExtn', fieldName: 'pltwtrImprmnPlanExtn', header: { text: '관로정비\n계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'pltwtrPlanExtn', fieldName: 'pltwtrPlanExtn', header: { text: '계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'pltwtrFlctyExtn', fieldName: 'pltwtrFlctyExtn', header: { text: '시설연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'pltwtrSqreExtn', fieldName: 'pltwtrSqreExtn', header: { text: '사각형(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'pltwtrCiclExtn', fieldName: 'pltwtrCiclExtn', header: { text: '원형(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, /* 분류식 - 우수관로 */ - { name: 'rainwtrPlanAr', fieldName: 'rainwtrPlanAr', header: { text: '관로정비\n계획면적(㎢)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'rainwtrImprmnPlanExtn', fieldName: 'rainwtrImprmnPlanExtn', header: { text: '관로정비\n계획연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'rainwtrPlanExtn', fieldName: 'rainwtrPlanExtn', header: { text: '계획연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'rainwtrFlctyExtn', fieldName: 'rainwtrFlctyExtn', header: { text: '시설연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'rainwtrSqreExtn', fieldName: 'rainwtrSqreExtn', header: { text: '사각형(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'rainwtrCiclExtn', fieldName: 'rainwtrCiclExtn', header: { text: '원형(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'rainwtrOpswrExtn', fieldName: 'rainwtrOpswrExtn', header: { text: '개거(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'rainwtrSdgttrExtn', fieldName: 'rainwtrSdgttrExtn', header: { text: '측구(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrPlanAr', fieldName: 'rainwtrPlanAr', header: { text: '관로정비\n계획면적(㎢)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'rainwtrImprmnPlanExtn', fieldName: 'rainwtrImprmnPlanExtn', header: { text: '관로정비\n계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrPlanExtn', fieldName: 'rainwtrPlanExtn', header: { text: '계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrFlctyExtn', fieldName: 'rainwtrFlctyExtn', header: { text: '시설연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrSqreExtn', fieldName: 'rainwtrSqreExtn', header: { text: '사각형(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrCiclExtn', fieldName: 'rainwtrCiclExtn', header: { text: '원형(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrOpswrExtn', fieldName: 'rainwtrOpswrExtn', header: { text: '개거(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrSdgttrExtn', fieldName: 'rainwtrSdgttrExtn', header: { text: '측구(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, /* 부속시설 */ - { name: 'mnhleSite', fieldName: 'mnhleSite', header: { text: '합류식맨홀(개소)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'pltwtrMnhleSite', fieldName: 'pltwtrMnhleSite', header: { text: '오수맨홀(개소)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'rainwtrMnhleSite', fieldName: 'rainwtrMnhleSite', header: { text: '우수맨홀(개소)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'pltwtrRcvrSite', fieldName: 'pltwtrRcvrSite', header: { text: '오수받이(개소)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'rainwtrRcvrSite', fieldName: 'rainwtrRcvrSite', header: { text: '빗물받이(개소)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'soilspllSite', fieldName: 'soilspllSite', header: { text: '우수토실(개소)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'soilholeSite', fieldName: 'soilholeSite', header: { text: '우수토구(개소)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'mnhleSite', fieldName: 'mnhleSite', header: { text: '합류식맨홀(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrMnhleSite', fieldName: 'pltwtrMnhleSite', header: { text: '오수맨홀(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrMnhleSite', fieldName: 'rainwtrMnhleSite', header: { text: '우수맨홀(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrRcvrSite', fieldName: 'pltwtrRcvrSite', header: { text: '오수받이(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrRcvrSite', fieldName: 'rainwtrRcvrSite', header: { text: '빗물받이(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'soilspllSite', fieldName: 'soilspllSite', header: { text: '우수토실(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'soilholeSite', fieldName: 'soilholeSite', header: { text: '우수토구(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, /* 대장 및 기타 */ - { name: 'sewerRegstrCmptExtn', fieldName: 'sewerRegstrCmptExtn', header: { text: '하수도대장전산화연장(m)' }, footer: footerIntArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'sewerRegstrCmptRt', fieldName: 'sewerRegstrCmptRt', header: { text: '하수도대장전산화율(%)' }, footer: footerAvgDecimalArrayByDivision,valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'sewerRegstrCmptExtn', fieldName: 'sewerRegstrCmptExtn', header: { text: '하수도대장전산화연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sewerRegstrCmptRt', fieldName: 'sewerRegstrCmptRt', header: { text: '하수도대장전산화율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'areaGroup', header: { text: '행정구역' }, items: [ - { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, - 'gugun', - 'adstDong', - ] }, + { + name: 'areaGroup', header: { text: '행정구역' }, items: [ + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', + 'adstDong', + ] + }, 'displayYear', 'targetYr', 'piplImprmnPlanExtn', 'piplPlanExtn', 'piplFcltyExtn', 'piplSupprt', { diff --git a/apps/st/src/entities/statistics/api/statistics-input-service.ts b/apps/st/src/entities/statistics/api/statistics-input-service.ts index 32cae5723..bf758e0a8 100644 --- a/apps/st/src/entities/statistics/api/statistics-input-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-input-service.ts @@ -6,6 +6,8 @@ import { type SewerageSupplyRateRequestParams, type SewerageSupplyRateApiResponse, type CheckInputPeriodResponse, + type SewerageSupplyRateSaveItem, + type SewerageSupplyRateSaveApiResponse, } from "../model/statistics-input-interface"; export class StatisticsInputAPiService implements StatisticsInputService { @@ -27,6 +29,15 @@ export class StatisticsInputAPiService implements StatisticsInputService { return response.data; } + + public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveItem[]): Promise { + const response = await axios.post ( + `${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`, + params + ); + + return response.data; + } } export const statisticsInputAPi = new StatisticsInputAPiService(); \ No newline at end of file diff --git a/apps/st/src/entities/statistics/api/statistics-management-service.ts b/apps/st/src/entities/statistics/api/statistics-management-service.ts index b566d19c3..a3d46b68b 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -67,7 +67,7 @@ export class StatisticsManagementApiService { } ): Promise { const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/pipe/tab1`, + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/regional-status`, { params } ); return response.data; diff --git a/apps/st/src/entities/statistics/model/statistics-input-interface.ts b/apps/st/src/entities/statistics/model/statistics-input-interface.ts index 6a76d567e..4f36f49b3 100644 --- a/apps/st/src/entities/statistics/model/statistics-input-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-input-interface.ts @@ -13,6 +13,28 @@ export interface SewerageSupplyRateStatusInfo { returnOpinionSm: string | null; } +export interface SewerageSupplyRateCommonInfo { + bsnmArea: number; + bsnmCd: string; + bsnmFrgnrPoplSm: number; + bsnmPoplSm: number; + bsnmTotalPopl: number; + piplFcltyExtn: number; + piplPlanExtn: number; + piplSupprt: number; + sido: string; + yr: string; +} + +/** 검증 결과 한 건 (필드별 메시지) */ +export interface VerificationResultItem { + type: string; + rsn: string; +} + +/** 필드명 → 검증 결과 배열. 예: { popltnSm: [{ type, rsn }], wstrPrmTrt: [...] } */ +export type SewerageSupplyRateVerificationResult = Record; + export interface SewerageSupplyRateItem { sido: string; gugun: string; @@ -93,11 +115,13 @@ export interface SewerageSupplyRateItem { chkAdtrtPopltnSuprt: string; chkSwrgInstlrt: string; chkNotConectPopltn: string; + verificationResult?: SewerageSupplyRateVerificationResult; } export interface SewerageSupplyRateResponseData { statusInfo: SewerageSupplyRateStatusInfo; sewerageSupplyRateList: SewerageSupplyRateItem[]; + commonInfo: SewerageSupplyRateCommonInfo; } export type SewerageSupplyRateApiResponse = ApiResponse & { @@ -109,10 +133,53 @@ export interface SewerageSupplyRateRequestParams { gugun?: string; bsnmCd?: string; yr?: string; - demoYr?: string; mode?: string; } +/** 하수도 보급률 저장 API 요청 body (한 행) */ +export interface SewerageSupplyRateSaveItem { + bsnmCd: string; + sido: string; + gugun: string | null; + yr: string; + adstCd: string; + adstDong: string; + bstrm: string; + speclCntrplnAr: string | null; + pblswrgPrmTrt: number; + pblswrgScdTrt: number; + pblswrgTertTrt: number; + wstrPrmTrt: number; + wstrScdTrt: number; + wstrTertTrt: number; + notConectPopltn: number; + swgeProcZoneInnerAr: number; + townUnprocPopltn: number; + townSwttPopltn: number; + townWrrtnPopltn: number; + swgeProcZoneOuterAr: number; + rmk: string; + popltnSm: number; + arSm: number; + swgeProcZoneInnerSm: number; + pblswrgPopltnSm: number; + wstrPopltnSm: number; + swgeProcZoneOuterSm: number; + pblswrgPopltnSuprt: number; + adtrtPopltnSuprt: number; + swrgInstlrt: number; + userId: string; +} + +export interface SewerageSupplyRateSaveData { + hasMismatch?: boolean; + mismatchIdList?: string[]; + httpStatus?: number; + code?: string; +} + +export type SewerageSupplyRateSaveApiResponse = ApiResponse; + // 하수도 통계 입력 기간 조회 export interface CheckInputPeriodResponse { status: string; @@ -122,4 +189,6 @@ export interface CheckInputPeriodResponse { export interface StatisticsInputService { getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise; + checkInputPeriod(params: string): Promise; + saveSewerageSupplyRate(params: SewerageSupplyRateSaveItem[]): Promise; } \ No newline at end of file