diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index adf9f9c72..1ee114f97 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -221,7 +221,7 @@ function createOnSortingHandler( 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( @@ -412,6 +412,7 @@ const DataGrid = ({ showHeaderCheckbox = false, rowData, setRowData, + getGridDataRef, onRowClick, }: DataGridProps) => { const { columns, columnLayout } = columnInfo; @@ -1030,7 +1031,46 @@ const DataGrid = ({ } }); }, 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, + ]); // 행 클릭 이벤트 설정 useEffect(() => { @@ -1117,20 +1157,25 @@ const DataGrid = ({ sortDirRef, originalRowsRef, ); - const dp = gridRef.current?.dataProvider as + const dataProvider = gridRef.current + ?.dataProvider as unknown as | { - setFields?: (fields: unknown[]) => void; - setRows?: ( - rows: Record[], - ) => void; + setFields?(f: unknown[]): void; + setRows?( + r: Record[], + ): void; + getJsonRow?( + r: number, + ): Record; } | undefined; - if (dp?.setFields) { - dp.setFields(buildFieldsFromColumns(columns)); + 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..71d94fad3 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) || []; @@ -653,22 +656,115 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r }, [rowDataMap, columnInfo]); - // 활성 탭이 변경될 때마다 fitStyle 재적용 (그리드가 보일 때) + // 탭 전환 시 그리드 레이아웃·푸터·포맷 재적용 (여러 시점 + ResizeObserver) + const appliedTabRef = useRef(null); + useEffect(() => { - const gridRef = gridRefs.current.get(activeTab); - if (!gridRef?.gridView) return; + appliedTabRef.current = null; + const currentInfo = columnInfo.find(info => info.tabKey === activeTab); + const columns = currentInfo?.columns; + const columnLayout = currentInfo?.columnLayout; + if (!columns?.length || !columnLayout) return; - const gridView = gridRef.gridView; - // 약간의 지연을 두고 설정 (그리드 렌더링 완료 후) - const timer = setTimeout(() => { - gridView.setDisplayOptions({ - fitStyle: GridFitStyle.EVEN + const tryApply = () => { + if (appliedTabRef.current === activeTab) return; + const gridRef = gridRefs.current.get(activeTab); + if (!gridRef?.gridView || !gridRef?.dataProvider) return; + + const gridView = gridRef.gridView; + const dp = gridRef.dataProvider as { setFields?: (fields: unknown[]) => void; setRows?: (rows: Record[]) => void } | undefined; + const tabRowData = rowDataMap.get(activeTab) || []; + + try { + if (dp?.setFields) dp.setFields(buildFieldsFromColumns(columns)); + + gridView.setDisplayOptions({ fitStyle: GridFitStyle.EVEN }); + gridView.setColumnLayout?.(columnLayout as ColumnLayoutItem[]); + + const hasFooter = columns.some(col => col.footer); + if (hasFooter) { + const hasArrayFooter = columns.some(col => col.footer && Array.isArray(col.footer)); + const hasMultiRowFooter = columns.some(col => { + const f = col.footer; + return f && !Array.isArray(f) && 'multiRow' in f && (f as { multiRow?: boolean }).multiRow === true; + }); + if (hasArrayFooter || hasMultiRowFooter) { + const lengths = columns.filter(col => col.footer && Array.isArray(col.footer)).map(col => (col.footer as unknown[]).length); + const maxLen = lengths.length ? Math.max(...lengths) : 2; + gridView.setFooters?.(Array.from({ length: maxLen }, () => ({ height: 40 }))); + } else { + gridView.footer.height = 40; + } + } + gridView.setOptions?.({ summaryMode: "aggregate" }); + + if (columns.some(col => col.footer && Array.isArray(col.footer))) { + const columnsWithFooter: RealGridColumnInfo[] = columns.map(col => { + const c: RealGridColumnInfo = { + name: col.name, + fieldName: col.fieldName, + header: col.header, + editable: col.editable, + valueType: col.valueType, + dataFormat: col.dataFormat, + ...(col.styleName != null && { styleName: col.styleName }), + ...(col.mergeCells && { mergeRule: { criteria: 'value' as const } }), + }; + if (col.footer) { + if (Array.isArray(col.footer)) c.footer = col.footer; + else c.footer = { text: col.footer.text, expression: col.footer.expression, numberFormat: col.footer.numberFormat }; + } + return c; + }); + (gridView as GridViewWithSetColumns).setColumns?.(columnsWithFooter); + columns.forEach(col => { + if (!col.footer || !Array.isArray(col.footer)) return; + try { + const column = gridView.columnByName(col.name); + const footerArr = column?.footer as unknown[] | undefined; + if (footerArr && Array.isArray(footerArr)) + col.footer.forEach((item, i) => { + if (item.valueCallback && footerArr[i]) + (footerArr[i] as FooterItemWithCallback).valueCallback = item.valueCallback; + }); + } catch (_) {} + }); + } + columns.forEach(col => { + if (!col.footer || Array.isArray(col.footer)) return; + try { + const column = gridView.columnByName(col.name); + const fo = col.footer as { valueCallback?: (g: unknown, col: unknown, fi: number, cf: unknown, v: unknown) => unknown }; + if (column?.footer && fo.valueCallback) + (column.footer as { valueCallback?: typeof fo.valueCallback }).valueCallback = fo.valueCallback; + } catch (_) {} + }); + + if (dp?.setRows && tabRowData.length) dp.setRows(tabRowData); + gridView.refresh(); + appliedTabRef.current = activeTab; + } catch (e) { + console.warn('TabDataGrid activeTab layout re-apply:', e); + } + }; + + const delays = [200, 500, 1000, 1500]; + const timers = delays.map(d => setTimeout(tryApply, d)); + + const container = document.querySelector(`[data-tab-key="${activeTab}"]`); + let observer: ResizeObserver | null = null; + if (container && typeof ResizeObserver !== 'undefined') { + observer = new ResizeObserver(() => { + if ((container as HTMLElement).offsetWidth > 0) tryApply(); }); - gridView.refresh(); - }, 50); + observer.observe(container); + } - return () => clearTimeout(timer); - }, [activeTab]); + return () => { + timers.forEach(t => clearTimeout(t)); + observer?.disconnect(); + }; + }, [activeTab, columnInfo, rowDataMap]); if (columnInfo.length === 0) { return
컬럼 정보가 없습니다.
; @@ -788,7 +884,9 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r }
+ {activeTab === tabKey ? ( { if (el) { gridRefs.current.set(tabKey, el); @@ -803,10 +901,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 @@ -1018,7 +1121,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r const onSortingHandler = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef, findGridElement); (gridView as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = onSortingHandler; - }, 0); + }, 150); } } else { gridRefs.current.delete(tabKey); @@ -1053,6 +1156,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r /> ))} + ) : null}
diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 7644ab1e3..9333b92cb 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,31 @@ 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, + piplSupprt: commonInfo.piplSupprt, })); - 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 +159,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 +272,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..34fe00910 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, useRef } from "react"; import TabDataGrid from "@/components/module/TabDataGrid"; import SearchForm from "@/components/module/SearchForm"; import SaveButton from "@/components/button/SaveButton"; @@ -10,19 +10,23 @@ import { LoadingWithAnimation } from "@common/components"; import { useAlertStore } from "@/shared/lib"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn"; +import type { ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { toGridRows } from "@/components/util/grid/gridFunction"; // 하수도 통계 검토 02.하수관로 const SewagePipeRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); - const [rowDataMap, setRowDataMap] = useState>(new Map()); + const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); const [isLoading, setIsLoading] = useState(false); + const [activeTab, setActiveTab] = useState('a'); + + const searchParamRef = useRef(null); const columnInfo = useMemo(() => { - return SewagePipeColumnInfoListRvw.map((tab: any) => ({ + return SewagePipeColumnInfoListRvw.map((tab: ColumnInfoList) => ({ ...tab, - columns: tab.columns.map((col: any) => ({ + columns: (tab.columns ?? []).map((col: ColumnConfig) => ({ ...col, editable: false as const, styleName: col.styleName ?? ("rg-cell-readonly" as const), @@ -30,7 +34,10 @@ const SewagePipeRvw = () => { })); }, []); - const handleReset = () => setRowDataMap(new Map()); + const handleReset = () => { + setRowDataMap(new Map()); + setActiveTab('a'); + }; const handleSearch = async (values: SearchFormValues) => { if (!values.sido || values.sido === 'ALL') { @@ -38,60 +45,101 @@ const SewagePipeRvw = () => { return; } + setActiveTab('a'); + + setRowDataMap(new Map()); + 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) { - const rawList = response.data; - const newRowDataMap = new Map(); - - newRowDataMap.set('a', toGridRows(rawList)); - + searchParamRef.current = searchParam; + const response = await statisticsManagementApi.getSewagePipeReviewRegionalList(searchParam); + + if (response?.status === 'success' && response.data) { + const newRowDataMap = new Map[]>(); + newRowDataMap.set('a', toGridRows(response.data as object[])); setRowDataMap(newRowDataMap); } else { - handleReset(); - alert(response.message || "데이터가 없습니다."); + alert(response?.message || "데이터가 없습니다."); } } catch (error) { - console.error("조회 에러:", error); alert('데이터 조회 중 오류가 발생했습니다.'); - handleReset(); } finally { setIsLoading(false); } }; + const handleActiveTabChange = async (tabKey: string) => { + setActiveTab(tabKey); + + const existingAData = rowDataMap.get('a'); + const activeTabData = rowDataMap.get(tabKey); + + const hasAData = Array.isArray(existingAData) && existingAData.length > 0; + const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0; + + if (hasAData && !hasActiveTabData && searchParamRef.current) { + + try { + setIsLoading(true); + + let response; + + if (tabKey === 'b') { + response = await statisticsManagementApi.getSewagePipeFacilityList(searchParamRef.current); + } + + if (response?.status === 'success' && response.data) { + setRowDataMap(prev => { + const next = new Map(prev); + next.set(tabKey, toGridRows(response.data as object[])); + return next; + }); + } else { + alert(response?.message || "데이터가 없습니다."); + } + } catch { + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + } + }; + 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/HbStatisticsVerification.tsx b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx index 0e4c38362..81fa83b2a 100644 --- a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx +++ b/apps/st/src/components/statistics/verification/HbStatisticsVerification.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 9e1f3f521..23d34020f 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -214,6 +214,10 @@ export type DataGridProps = { setRowData: React.Dispatch< React.SetStateAction[]> >; + /** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */ + getGridDataRef?: { + current: (() => Record[]) | null; + }; onRowClick?: (rowData: Record) => void; // 행 클릭 이벤트 }; @@ -229,6 +233,10 @@ export type TabDataGridProps = { React.SetStateAction[]>> >; 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..7bcb3a41f 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -18,7 +18,188 @@ const rawList = [ mergeCells: true, editable: false, sortdisable: true, - footer: [ + 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 '합계'; + } + }, + { + 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 '합계'; + } + } + ] + }, + { 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)); + } + } + 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)); + } + } + 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' }, + + /* 합류식(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: '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: '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: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ + { + name: 'areaGroup', header: { text: '행정구역' }, items: [ + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', + 'adstDong', + ] + }, + 'displayYear', + 'targetYr', 'piplImprmnPlanExtn', 'piplPlanExtn', 'piplFcltyExtn', 'piplSupprt', + { + name: 'combinedGroup', header: { text: '합류식(m)' }, + items: ['cmbnPlanAr', 'cmbnImprmnPlanExtn', 'cmbnPlanExtn', 'cmbnFcltyExtn', + { name: 'cmbnAmGroup', header: { text: '암거' }, items: ['cmbnRctglExtn', 'cmbnCicleExtn'] }, + 'cmbnOpswrExtn', 'cmbnSdgttrExtn' + ], + }, + { + name: 'sepGroup', header: { text: '분류식(m)' }, + items: [ + { + name: 'foulPipeGroup', header: { text: '오수관로' }, + items: ['pltwtrPlanAr', 'pltwtrImprmnPlanExtn', 'pltwtrPlanExtn', 'pltwtrFlctyExtn', + { name: 'foulAmGroup', header: { text: '암거' }, items: ['pltwtrSqreExtn', 'pltwtrCiclExtn'] }] + }, + { + name: 'rainPipeGroup', header: { text: '우수관로' }, + items: ['rainwtrPlanAr', 'rainwtrImprmnPlanExtn', 'rainwtrPlanExtn', 'rainwtrFlctyExtn', + { name: 'rainAmGroup', header: { text: '암거' }, items: ['rainwtrSqreExtn', 'rainwtrCiclExtn'] }, + 'rainwtrOpswrExtn', 'rainwtrSdgttrExtn'] + } + ], + }, + 'mnhleSite', + { name: 'fcltyGroup', header: { text: '부속시설(개소)' }, items: ['pltwtrMnhleSite', 'rainwtrMnhleSite'] }, + 'pltwtrRcvrSite', 'rainwtrRcvrSite', 'soilspllSite', 'soilholeSite', 'sewerRegstrCmptExtn', 'sewerRegstrCmptRt', 'rmk' + ] + }, + { + tabKey: 'b', + tabLabel: '시설별 설치현황', + rowStartNum: 4, + fixedColumns: 3, + columns: [ + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + editable: false, + sortdisable: true, + footer: [ { valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { const divisionIndex = 0; @@ -32,8 +213,7 @@ const rawList = [ } } const divisionValues = Array.from(uniqueValues); - if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; - return '합계'; + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; } }, { @@ -49,15 +229,14 @@ const rawList = [ } } const divisionValues = Array.from(uniqueValues); - if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; - return '합계'; + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; } } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, editable: false }, - { + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'bscFctNm', fieldName: 'bscFctNm', header: { text: '하수처리장명' }, editable: false }, + { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, @@ -98,164 +277,80 @@ const rawList = [ } ] }, + { name: 'fctCpc', fieldName: 'fctCpc', header: { text: '하수처리\n시설용량\n(㎥/일)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - /* 하수정비 기본계획 */ - { 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: 'procZoneSm', fieldName: 'procZoneSm', header: { text: '합계' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'cmbnProcZoneAr', fieldName: 'cmbnProcZoneAr', header: { text: '합류식' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'sprtProcZoneAr', fieldName: 'sprtProcZoneAr', header: { text: '분류식' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'sepRate', fieldName: 'sepRate', header: { text: '분류식화(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - /* 합류식(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: 'drngEqpSm', fieldName: 'drngEqpSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'cmbnDrngEqp', fieldName: 'cmbnDrngEqp', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprtDrngEqpSm', fieldName: 'sprtDrngEqpSm', header: { text: '합계(분류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprtDrngEqpCnnc', fieldName: 'sprtDrngEqpCnnc', header: { text: '연결' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprtDrngEqpDcnnc', fieldName: 'sprtDrngEqpDcnnc', header: { text: '미연결' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - /* 분류식 - 오수관로 */ - { 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: 'piplFcltyExtn', fieldName: 'piplFcltyExtn', header: { text: '총\n시설연장\n(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'icgMnnSm', fieldName: 'icgMnnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'icgExtn', fieldName: 'icgExtn', header: { text: '차집' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'mnnExtn', fieldName: 'mnnExtn', header: { text: '오수간선' }, 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: 'cmbnSoilholeSite', fieldName: 'cmbnSoilholeSite', header: { text: '우수토실(합류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprtSoilholeSite', fieldName: 'sprtSoilholeSite', header: { text: '우수토실(분류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprtSoilspllSite', fieldName: 'sprtSoilspllSite', 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' }, + /* 지선(m) */ + { name: 'brnchlnSm', fieldName: 'brnchlnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnBrnchlnExtn', fieldName: 'cmbnBrnchlnExtn', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrBrnchlnExtn', fieldName: 'rainwtrBrnchlnExtn', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'pltwtrBrnchlnExtn', fieldName: 'pltwtrBrnchlnExtn', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - /* 대장 및 기타 */ - { 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: [ + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ + { + name: 'addrGroup', + header: { text: '행정구역' }, + items: [ { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, - 'gugun', - 'adstDong', - ] }, - 'displayYear', - 'targetYr', 'piplImprmnPlanExtn', 'piplPlanExtn', 'piplFcltyExtn', 'piplSupprt', - { - name: 'combinedGroup', header: { text: '합류식(m)' }, - items: ['cmbnPlanAr', 'cmbnImprmnPlanExtn', 'cmbnPlanExtn', 'cmbnFcltyExtn', - { name: 'cmbnAmGroup', header: { text: '암거' }, items: ['cmbnRctglExtn', 'cmbnCicleExtn'] }, - 'cmbnOpswrExtn', 'cmbnSdgttrExtn' - ], - }, - { - name: 'sepGroup', header: { text: '분류식(m)' }, - items: [ - { - name: 'foulPipeGroup', header: { text: '오수관로' }, - items: ['pltwtrPlanAr', 'pltwtrImprmnPlanExtn', 'pltwtrPlanExtn', 'pltwtrFlctyExtn', - { name: 'foulAmGroup', header: { text: '암거' }, items: ['pltwtrSqreExtn', 'pltwtrCiclExtn'] }] - }, - { - name: 'rainPipeGroup', header: { text: '우수관로' }, - items: ['rainwtrPlanAr', 'rainwtrImprmnPlanExtn', 'rainwtrPlanExtn', 'rainwtrFlctyExtn', - { name: 'rainAmGroup', header: { text: '암거' }, items: ['rainwtrSqreExtn', 'rainwtrCiclExtn'] }, - 'rainwtrOpswrExtn', 'rainwtrSdgttrExtn'] - } - ], - }, - 'mnhleSite', - { name: 'fcltyGroup', header: { text: '부속시설(개소)' }, items: ['pltwtrMnhleSite', 'rainwtrMnhleSite'] }, - 'pltwtrRcvrSite', 'rainwtrRcvrSite', 'soilspllSite', 'soilholeSite', 'sewerRegstrCmptExtn', 'sewerRegstrCmptRt', 'rmk' - ] - }, - { - tabKey: 'b', - tabLabel: '시설별 설치현황', - rowStartNum: 5, - fixedColumns: 4, - columns: [ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: '구분', fieldName: '구분', header: { text: '구분' } }, - { name: '하수처리장명', fieldName: '하수처리장명', header: { text: '하수처리장명' } }, - { name: 'FCT_CPC', fieldName: '하수처리시설용량(㎥/일)', header: { text: '하수처리\n시설용량\n(㎥/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - { name: 'PROC_ZONE_SM', fieldName: '합계(면적)', header: { text: '합계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'CMBN_PROC_ZONE_AR', fieldName: '합류식(면적)', header: { text: '합류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'SPRT_PROC_ZONE_AR', fieldName: '분류식(면적)', header: { text: '분류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - - { name: 'SEP_RATE', fieldName: '분류식화', header: { text: '분류식화' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - { name: 'DRNG_EQP_SM', fieldName: '합계(배수)', header: { text: '합계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'CMBN_DRNG_EQP', fieldName: '합류식(배수)', header: { text: '합류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SPRT_DRNG_EQP_SM', fieldName: '합계(분류배수)', header: { text: '합계(분류)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SPRT_DRNG_EQP_CNNC', fieldName: '연결', header: { text: '연결' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SPRT_DRNG_EQP_DCNNC', fieldName: '미연결', header: { text: '미연결' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - - { name: 'PIPL_FCLTY_EXTN', fieldName: '총 시설연장(m)', header: { text: '총\n시설연장\n(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - - { name: 'ICG_MNN_SM', fieldName: '합계(차집)', header: { text: '합계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'ICG_EXTN', fieldName: '차집', header: { text: '차집' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'MNN_EXTN', fieldName: '오수간선', header: { text: '오수간선' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - - { name: '합류식', fieldName: '합류식(토실)', header: { text: '합류식' } }, - { name: 'CMBN_SOILHOLE_SITE', fieldName: '우수토실(합류)', header: { text: '우수토실(합류)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SPRT_SOILHOLE_SITE', fieldName: '우수토실(분류)', header: { text: '우수토실(분류)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SPRT_SOILSPLL_SITE', fieldName: '우수토구(분류)', header: { text: '우수토구(분류)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - - { name: 'BRNCHLN_SM', fieldName: '합계(지선)', header: { text: '합계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'CMBN_BRNCHLN_EXTN', fieldName: '합류식(지선)', header: { text: '합류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'RAINWTR_BRNCHLN_EXTN', fieldName: '우수(지선)', header: { text: '우수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'PLTWTR_BRNCHLN_EXTN', fieldName: '오수(지선)', header: { text: '오수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, - ], - columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - '하수처리장명', 'FCT_CPC', '구분', - { name: 'areaGroup', header: { text: '처리구역면적(㎢)' }, items: ['PROC_ZONE_SM', 'CMBN_PROC_ZONE_AR', 'SPRT_PROC_ZONE_AR'] }, - 'SEP_RATE', - { - name: 'drainGroup', header: { text: '배수설비(개소)' }, - items: ['DRNG_EQP_SM', 'CMBN_DRNG_EQP', { name: 'sepDrainGroup', header: { text: '분류식' }, items: ['SPRT_DRNG_EQP_SM', 'SPRT_DRNG_EQP_CNNC', 'SPRT_DRNG_EQP_DCNNC'] }], - }, - { - name: 'pipeGroup', header: { text: '하수관로' }, - items: [ - 'PIPL_FCLTY_EXTN', - { name: 'trunkGroup', header: { text: '차집/오수간선(m)' }, items: ['ICG_MNN_SM', 'ICG_EXTN', 'MNN_EXTN'] }, - { - name: 'siteGroup', header: { text: '토실/토구(개소)' }, - items: [ - { name: 'cmbnSiteGroup', header: { text: '합류식' }, items: ['CMBN_SOILHOLE_SITE'] }, - { name: 'sepSiteGroup', header: { text: '분류식' }, items: ['SPRT_SOILHOLE_SITE', 'SPRT_SOILSPLL_SITE'] } - ] - }, - { - name: 'branchGroup', header: { text: '지선(m)' }, - items: ['BRNCHLN_SM', 'CMBN_BRNCHLN_EXTN', { name: 'sepBranchGroup', header: { text: '분류식' }, items: ['RAINWTR_BRNCHLN_EXTN', 'PLTWTR_BRNCHLN_EXTN'] }] - } - ], - }, - 'RMK' - ] - }, + 'gugun', + 'bscFctNm', + ] + }, + + 'fctCpc', + 'displayYear', + { name: 'areaGroup', header: { text: '처리구역면적(㎢)' }, items: ['procZoneSm', 'cmbnProcZoneAr', 'sprtProcZoneAr'] }, + 'sepRate', + { + name: 'drainGroup', header: { text: '배수설비(개소)' }, + items: ['drngEqpSm', 'cmbnDrngEqp', { name: 'sepDrainGroup', header: { text: '분류식' }, items: ['sprtDrngEqpSm', 'sprtDrngEqpCnnc', 'sprtDrngEqpDcnnc'] }], + }, + { + name: 'pipeGroup', header: { text: '하수관로' }, + items: [ + 'piplFcltyExtn', + { name: 'trunkGroup', header: { text: '차집/오수간선(m)' }, items: ['icgMnnSm', 'icgExtn', 'mnnExtn'] }, + { + name: 'siteGroup', header: { text: '토실/토구(개소)' }, + items: [ + { name: 'cmbnSiteGroup', header: { text: '합류식' }, items: ['cmbnSoilholeSite'] }, + { name: 'sepSiteGroup', header: { text: '분류식' }, items: ['sprtSoilholeSite', 'sprtSoilspllSite'] } + ] + }, + { + name: 'branchGroup', header: { text: '지선(m)' }, + items: ['brnchlnSm', 'cmbnBrnchlnExtn', { name: 'sepBranchGroup', header: { text: '우수/오수' }, items: ['rainwtrBrnchlnExtn', 'pltwtrBrnchlnExtn'] }] + } + ], + }, + 'rmk' + ] +}, { tabKey: 'c', tabLabel: '관종별 현황', 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..89718aaf0 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -6,12 +6,15 @@ import { type StatsInputStatusReviewApiResponse } from "../model/statistics-input-review-interface"; import { - type SewagePipeReviewApiResponse + type SewagePipeReviewRegionalApiResponse } from "../model/statistics-sewage-pipe-review-interface"; import { type SewerageSupplyRateReviewRequestParams, type SewerageSupplyRateReviewApiResponse, } from "../model/statistics-sewerage-penetration-review-interface"; +import { + type SewagePipeFacilityApiResponse +} from "../model/statistics-sewage-pipe-review-interface"; export class StatisticsManagementApiService { /** @@ -58,16 +61,34 @@ export class StatisticsManagementApiService { * 02. 하수관로 지역별 시설현황 목록 조회 (Tab1) * @param params (sido, gugun, yr, bsnmCd) */ - public async getSewagePipeReviewList( + public async getSewagePipeReviewRegionalList( params: { yr?: string; bsnmCd?: string; sido?: string; gugun?: string; } - ): Promise { - const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/pipe/tab1`, + ): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/regional-status`, + { params } + ); + return response.data; + } + /** + * 02. 하수관로 시설별 설치현황 목록 조회 (Tab2) + * @param params (sido, gugun, yr, bsnmCd) + */ + public async getSewagePipeFacilityList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/facility-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 diff --git a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts index 581d31ba1..0d82096ad 100644 --- a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts @@ -1,9 +1,9 @@ import type { ApiResponse } from "./statistics-interface"; /** - * 02. 하수관로 지역별 시설현황 아이템 인터페이스 (Tab1) + * 02. 하수관로 tab1.지역별 시설현황 아이템 인터페이스 (Tab1) */ -export interface SewagePipeReviewItem { +export interface SewagePipeReviewrRegionalItem { // 기본 정보 및 행 제어 sido: string; // 시도 gugun: string; // 시군구 @@ -64,11 +64,11 @@ export interface SewagePipeReviewItem { } export interface SewagePipeReviewResponseData { - sewagePipeList: SewagePipeReviewItem[]; + sewagePipeList: SewagePipeReviewrRegionalItem[]; } -export type SewagePipeReviewApiResponse = ApiResponse & { +export type SewagePipeReviewRegionalApiResponse = ApiResponse & { message: string; }; @@ -79,5 +79,74 @@ export interface SewagePipeReviewService { bsnmCd?: string; // 사업장 코드 sido?: string; // 시도 gugun?: string; // 구군 - }): Promise; + }): Promise; +} + + +export interface SewagePipeReviewFacilityItem { + // 1. 행정구역 및 기본 정보 + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + adstDong: string; // 행정구역명 + yr: string; // 연도 + rowType: 'OLD' | 'CURR'; // 전년도/당해년도 구분 + displayYear: string; // 구분 표시용 (2024년확정 등) + + // 2. 시설 정보 + bscFctNm: string; // 하수처리장명 + fctCpc: number; // 하수처리시설용량(㎥/일) + + // 3. 처리구역면적(㎢) + procZoneSm: number; // 합계(면적) + cmbnProcZoneAr: number; // 합류식(면적) + sprtProcZoneAr: number; // 분류식(면적) + sepRate: number; // 분류식화 (%) + + // 4. 배수설비(개소) + drngEqpSm: number; // 합계(배수) + cmbnDrngEqp: number; // 합류식(배수) + sprtDrngEqpSm: number; // 합계(분류배수) + sprtDrngEqpCnnc: number; // 연결 + sprtDrngEqpDcnnc: number; // 미연결 + + // 5. 하수관로 (m) + piplFcltyExtn: number; // 총 시설연장(m) + icgMnnSm: number; // 합계(차집/오수간선) + icgExtn: number; // 차집 + mnnExtn: number; // 오수간선 + + // 6. 토실/토구 (개소) + cmbnSoilholeSite: number; // 우수토실(합류) + sprtSoilholeSite: number; // 우수토실(분류) + sprtSoilspllSite: number; // 우수토구(분류) + + // 7. 지선 (m) + brnchlnSm: number; // 합계(지선) + cmbnBrnchlnExtn: number; // 합류식(지선) + rainwtrBrnchlnExtn: number; // 우수(지선) + pltwtrBrnchlnExtn: number; // 오수(지선) + + // 비고 + rmk: string; // 비고 +} + + +export interface SewagePipeFacilityResponseData { + facilityPipeList: SewagePipeReviewFacilityItem[]; +} + +export type SewagePipeFacilityApiResponse = ApiResponse & { + message: string; +}; + + +export interface SewagePipeFacilityService { + + getSewagePipeFacilityList(params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업장 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + }): Promise; } \ No newline at end of file