diff --git a/.husky/pre-push b/.husky/pre-push index 82c73e102..929f5302d 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,3 +1,7 @@ #!/bin/sh BRANCH=$(git rev-parse --abbrev-ref HEAD) -pnpm turbo run build --filter="...[origin/$BRANCH]" +echo "Branch: $BRANCH" +echo "Changed packages (dry-run):" +pnpm turbo run build --filter="...[origin/$BRANCH]" --dry-run +echo "Starting build..." +pnpm turbo run build --filter="...[origin/$BRANCH]" \ No newline at end of file diff --git a/apps/st/eslint.config.mjs b/apps/st/eslint.config.mjs index 18107f0ca..55ef02fb5 100644 --- a/apps/st/eslint.config.mjs +++ b/apps/st/eslint.config.mjs @@ -1,6 +1,7 @@ +import { FlatCompat } from "@eslint/eslintrc"; import { dirname } from "path"; import { fileURLToPath } from "url"; -import { FlatCompat } from "@eslint/eslintrc"; +import baseEsLintConfig from "../../eslint.config.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -11,6 +12,7 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), + ...baseEsLintConfig, { rules: { "react-hooks/exhaustive-deps": "off", diff --git a/apps/st/middleware.ts b/apps/st/middleware.ts index 8b1ecb501..9a2a48cb1 100644 --- a/apps/st/middleware.ts +++ b/apps/st/middleware.ts @@ -12,10 +12,10 @@ import { const serverApiUrl = process.env.NEXT_PUBLIC_SERVER_API_URL ?? "http://localhost:3000"; -const linkBackendApiHost = process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST ?? "http://localhost:8080"; - const stServiceUrl = ST_SERVICE_URL; +const linkBackendApiHost = process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST ?? "http://localhost:8080"; + // CSP 헤더 설정 함수 (img-src만) function addSecurityHeaders(response: NextResponse): NextResponse { response.headers.set("Content-Security-Policy", "img-src 'self' data: https: blob:;"); @@ -28,41 +28,46 @@ export function middleware(req: NextRequest) { const logMessages: string[] = [ `[Middleware Request] ${new Date().toISOString()}`, - `[Environment]\n Mode: ${process.env.NODE_ENV}\n Port: ${PORT}\n Proxy Host: ${PROXY_HOST}\n Server API URL: ${SERVER_API_URL}\n Site ID: ${SITE_ID}`, + `[Environment]\n Mode: ${process.env.NODE_ENV}\n Port: ${PORT}\n Proxy Host: ${PROXY_HOST}\n Server API URL: ${SERVER_API_URL}\n Site ID: ${SITE_ID}\n ST Service URL: ${stServiceUrl}`, `[Request]\n Original Pathname: ${pathname}`, ]; - if (pathname.startsWith("/st-management-service") || pathname.startsWith("/st-input-service")) { + // ST Service 처리 (dc의 /server/dc-service 패턴과 동일) + if (pathname.startsWith("/server/st-management-service") || pathname.startsWith("/server/st-input-service")) { const requestHeaders = new Headers(req.headers); const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; - + const queryParams = searchParams.toString(); - const newUrl = `${stServiceUrl}${pathname}${ + const newUrl = `${stServiceUrl}${pathname.replace("/server", "")}${ queryParams ? `?${queryParams}` : "" }`; if (token) requestHeaders.set(CLAIM_NAME, token); if (userId) requestHeaders.set(AUTH_USER_ID, userId); - - // 디버깅을 위한 토큰 정보 로그 - logMessages.push(` Token Debug: ${token ? 'EXISTS' : 'MISSING'}`); - logMessages.push(` UserId Debug: ${userId ? 'EXISTS' : 'MISSING'}`); + + logMessages.push(` Token Debug: ${token ? "EXISTS" : "MISSING"}`); + logMessages.push(` UserId Debug: ${userId ? "EXISTS" : "MISSING"}`); logMessages.push(` ST Service Rewritten URL: ${newUrl}`); logMessages.push("\n--------------------------------\n"); console.log(logMessages.join("\n")); - + const response = NextResponse.rewrite(new URL(newUrl), { headers: requestHeaders, }); return addSecurityHeaders(response); } - if (pathname.startsWith("/server")) { + // 일반 Server API 처리 (ST 서비스 제외) + if ( + pathname.startsWith("/server") && + !pathname.startsWith("/server/st-management-service") && + !pathname.startsWith("/server/st-input-service") + ) { const requestHeaders = new Headers(req.headers); const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; - + const queryParams = searchParams.toString(); const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ queryParams ? `?${queryParams}` : "" @@ -138,5 +143,5 @@ export function middleware(req: NextRequest) { } export const config = { - matcher: ["/server/:path*", "/attach-service/:path*", "/api/link/:path*", "/st-management-service/:path*", "/st-input-service/:path*", "/"], + matcher: ["/server/:path*", "/attach-service/:path*", "/api/link/:path*", "/"], }; diff --git a/apps/st/package.json b/apps/st/package.json index cd951af0d..9880cab30 100644 --- a/apps/st/package.json +++ b/apps/st/package.json @@ -34,6 +34,7 @@ "@types/xlsx": "^0.0.36", "axios": "^1.8.1", "chart.js": "^4.4.8", + "chartjs-plugin-zoom": "^2.2.0", "dayjs": "^1.11.13", "js-cookie": "^3.0.5", "jszip": "^3.10.1", @@ -62,6 +63,7 @@ "@types/webpack": "^5.28.5", "eslint": "^9", "eslint-config-next": "15.1.7", + "eslint-plugin-react-hooks": "^5.0.0", "postcss": "^8", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.11", diff --git a/apps/st/src/app/styles/components.css b/apps/st/src/app/styles/components.css index 5b9742c0f..e69de29bb 100644 --- a/apps/st/src/app/styles/components.css +++ b/apps/st/src/app/styles/components.css @@ -1,129 +0,0 @@ -.card { - position: relative; - margin: 0.6rem; - box-sizing: border-box; - min-height: 2rem; - border-radius: 0.5rem; - padding: 0.6rem; - font-size: 1.3rem; - color: #666; -} - -.card_width10 { - width: calc(10% - 12px); -} - -.card_width20 { - width: calc(20% - 12px); -} - -.card_width25 { - width: calc(25% - 12px); -} - -.card_width30 { - width: calc(30% - 12px); -} - -.card_width33 { - width: calc(33.333% - 12px); -} - -.card_width40 { - width: calc(40% - 12px); -} - -.card_width45 { - width: calc(45% - 12px); -} - -.card_width50 { - width: calc(50% - 12px); -} - -.card_width60 { - width: calc(60% - 12px); -} - -.card_width66 { - width: calc(66.666% - 12px); -} - -.card_width70 { - width: calc(70% - 12px); -} - -.card_width75 { - width: calc(75% - 12px); -} - -.card_width80 { - width: calc(80% - 12px); -} - -.card_width90 { - width: calc(90% - 12px); -} - -.card_width100 { - width: calc(100% - 12px); -} - -.card_nopadding { - padding: 0 !important; -} - -.card_nomargin { - width: 100%; - margin: 0 !important; - padding: 0 !important; -} - -.topline { - border-top: 1px solid #ccc; -} - -.bottomline { - border-bottom: 1px solid #e6eef3; - background: #fff; -} - -.card-border { - border: 1px solid #dbe2e6; - background-color: rgba(255, 255, 255, 0.6); - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); -} - -.card-header { - margin-top: -0.6rem; - margin-left: -0.6rem; - margin-right: -0.6rem; - display: flex; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid #e1e7eb; - padding: 1rem; -} - -.card-footer { - margin-left: -0.6rem; - margin-right: -0.6rem; - margin-top: 0.5rem; - display: flex; - align-items: center; - justify-content: center; - border-top: 1px solid #e1e7eb; - padding-top: 1rem; - padding-bottom: 1rem; -} - -/* 블랙 테마 */ -[data-theme="black"] { - .card-border { - background-color: rgba(255, 255, 255, 0.065); - border-color: #38383a; - } - .card-header { - border-bottom-color: #3f4042; - } -} diff --git a/apps/st/src/components/button/CancelSubmitButton.tsx b/apps/st/src/components/button/CancelSubmitButton.tsx new file mode 100644 index 000000000..c8b7c510f --- /dev/null +++ b/apps/st/src/components/button/CancelSubmitButton.tsx @@ -0,0 +1,16 @@ +import { Button } from "@common/components"; + +interface CancelSubmitButtonProps { + cnacelSubmitFunc: () => void; +} + +const CancelSubmitButton = ({ cnacelSubmitFunc }: CancelSubmitButtonProps) => { + return ( + + ); +}; + +export default CancelSubmitButton; \ No newline at end of file diff --git a/apps/st/src/components/css/DataGrid.css b/apps/st/src/components/css/DataGrid.css index f602400c7..cd21091f9 100644 --- a/apps/st/src/components/css/DataGrid.css +++ b/apps/st/src/components/css/DataGrid.css @@ -4,9 +4,24 @@ .rg-grid .rg-header .rg-table .rg-header-group-cell { @apply !bg-[#466995] !text-[#fff]; } - .rg-grid .rg-header .rg-table .rg-header-cell { + .rg-grid .rg-header .rg-table { @apply !bg-[#5987bf] !text-[#fff]; } + + /* 상태바·체크바 헤더 */ + .rg-grid .rg-head .rg-table tr:first-child td:first-child, + .rg-grid .rg-head .rg-table tr:first-child td:nth-child(2), + .rg-grid .rg-header .rg-table tr:first-child td:first-child, + .rg-grid .rg-header .rg-table tr:first-child td:nth-child(2) { + @apply !bg-[#5987bf] !text-[#fff]; + } + + .rg-grid .rg-header .rg-table tr td, + .rg-grid .rg-head .rg-table tr td { + border: none; + border-bottom: 1px solid #e5e7eb; + } + .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell { @apply !bg-white; } @@ -21,6 +36,18 @@ @apply !bg-[#e3eaf2]; } + /* 진행상황: 소프트 인라인 요약 (툴바와 자연스럽게 blend) */ + .data-grid-status-bubble { + @apply rounded-lg border border-slate-200/80 bg-slate-50/90 px-4 py-2.5; + box-shadow: none; + } + .data-grid-status-bubble span { + @apply text-base text-slate-700; + } + .data-grid-status-bubble span + span { + @apply ml-4 border-l border-slate-200/70 pl-4; + } + [data-theme="black"] { .rg-grid .rg-header .rg-table .rg-header-group-cell { @apply !bg-[#3C4043] !text-[#fff]; @@ -28,6 +55,12 @@ .rg-grid .rg-header .rg-table .rg-header-cell { @apply !bg-[#62686d] !text-[#fff]; } + .rg-grid .rg-head .rg-table tr:first-child td:first-child, + .rg-grid .rg-head .rg-table tr:first-child td:nth-child(2), + .rg-grid .rg-header .rg-table tr:first-child td:first-child, + .rg-grid .rg-header .rg-table tr:first-child td:nth-child(2) { + @apply !bg-[#62686d] !text-[#fff]; + } .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell { @apply !bg-[#545556] !text-[#fff]; } @@ -40,6 +73,12 @@ .rg-grid .rg-header .rg-table .rg-header-cell { @apply !bg-[#8d8d94] !text-[#fff]; } + .rg-grid .rg-head .rg-table tr:first-child td:first-child, + .rg-grid .rg-head .rg-table tr:first-child td:nth-child(2), + .rg-grid .rg-header .rg-table tr:first-child td:first-child, + .rg-grid .rg-header .rg-table tr:first-child td:nth-child(2) { + @apply !bg-[#8d8d94] !text-[#fff]; + } } [data-theme="green"] { @@ -49,5 +88,11 @@ .rg-grid .rg-header .rg-table .rg-header-cell { @apply !bg-[#6bafae] !text-[#fff]; } + .rg-grid .rg-head .rg-table tr:first-child td:first-child, + .rg-grid .rg-head .rg-table tr:first-child td:nth-child(2), + .rg-grid .rg-header .rg-table tr:first-child td:first-child, + .rg-grid .rg-header .rg-table tr:first-child td:nth-child(2) { + @apply !bg-[#6bafae] !text-[#fff]; + } } } diff --git a/apps/st/src/components/module/CompactDataGrid.tsx b/apps/st/src/components/module/CompactDataGrid.tsx index 4377eb762..36a17b225 100644 --- a/apps/st/src/components/module/CompactDataGrid.tsx +++ b/apps/st/src/components/module/CompactDataGrid.tsx @@ -1,9 +1,9 @@ -import { useRef, useEffect } from "react" +import { useRef, useEffect, useLayoutEffect } from "react" import { ValueType } from "realgrid" import RealGrid, { GridFitStyle } from "realgrid" import { RealGridReact, RGDataColumn } from "realgrid-react" -import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback } from "../type/DataGrid.type" +import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback, CellStyleCallback } from "../type/DataGrid.type" import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks" // RealGrid 타입 확장 @@ -62,15 +62,36 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string }; } -const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, rowData, setRowData }: DataGridProps) => { +const CompactDataGrid = ({ + columnInfo, + fixedColumns, + showHeaderCheckbox = false, + rowData, + setRowData, + getGridDataRef, + cellStyleCallback, + onRowClick, +}: DataGridProps) => { const { columns, columnLayout } = columnInfo; const gridRef = useRef(null); + const rowDataRef = useRef[]>(rowData); + const cellStyleCallbackRef = useRef(cellStyleCallback); + rowDataRef.current = rowData; + cellStyleCallbackRef.current = cellStyleCallback; + + useLayoutEffect(() => { + if (!cellStyleCallback || !gridRef.current?.gridView) return; + gridRef.current.gridView.refresh(); + }, [cellStyleCallback, rowData]); useEffect(() => { if (!gridRef.current?.gridView || !gridRef.current?.dataProvider) return; const gridView = gridRef.current.gridView; const dataProvider = gridRef.current.dataProvider; + if ((dataProvider as { setRows?: (r: unknown[]) => void })?.setRows && rowData) { + (dataProvider as { setRows: (r: unknown[]) => void }).setRows(rowData); + } // setDataComparer로 숫자 정렬 (setFields 미사용 → 데이터 유실·필드 중복 오류 없음) const dp = dataProvider as RealGridDataProviderComparer; @@ -231,11 +252,11 @@ const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, // footer가 단일 객체이고 valueCallback이 있는 경우 columns.forEach(col => { if (!col.footer || Array.isArray(col.footer)) return; - + try { const column = currentGridView.columnByName(col.name); if (!column || !column.footer) return; - + if (col.footer.valueCallback) { (column.footer as { valueCallback?: typeof col.footer.valueCallback }).valueCallback = col.footer.valueCallback; } @@ -243,9 +264,75 @@ const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, console.warn(`Error setting footer valueCallback for column ${col.name}:`, e); } }); + + // cellStyleCallback이 있으면 컬럼별 styleCallback 설정 + if (cellStyleCallbackRef.current) { + columns.forEach((column) => { + const baseStyleName = column.styleName; + const gridCol = currentGridView.columnByName(column.name) as { + styleCallback?: (g: unknown, dataCell: { itemIndex?: number; index?: { itemIndex?: number }; dataField?: string; fieldName?: string }) => string; + } | null; + if (gridCol) { + gridCol.styleCallback = (_g, dataCell) => { + const itemIndex = dataCell?.index?.itemIndex ?? dataCell?.itemIndex ?? -1; + const fieldName = (dataCell?.dataField ?? dataCell?.fieldName ?? column.fieldName) ?? ''; + const data = rowDataRef.current; + if (itemIndex < 0 || itemIndex >= data.length) return baseStyleName ?? ''; + const row = data[itemIndex] as Record; + const extra = cellStyleCallbackRef.current?.({ rowIndex: itemIndex, fieldName, row }); + if (!extra) return baseStyleName ?? ''; + return baseStyleName ? `${extra} ${baseStyleName}` : extra; + }; + } + }); + } + currentGridView.refresh?.(); }, 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(() => { + const gridView = gridRef.current?.gridView; + if (!gridView || !onRowClick) return; + + gridView.onCellClicked = (grid, clickData) => { + if (clickData.cellType !== "data" || clickData.itemIndex === undefined) return; + try { + const row = rowDataRef.current[clickData.itemIndex]; + onRowClick((row ?? {}) as Record); + } catch (error) { + console.error("행 데이터 가져오기 오류:", error); + } + }; + + return () => { + gridView.onCellClicked = undefined; + }; + }, [onRowClick]); return (
@@ -255,11 +342,16 @@ const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, autoGenerateField={true} layout={columnLayout} rows={rowData} - onInitialized={(grid) => { - const dp = gridRef.current?.dataProvider as { setFields?: (fields: unknown[]) => void } | undefined; + onInitialized={() => { + const dp = gridRef.current?.dataProvider as + | { setFields?(f: unknown[]): void; setRows?(r: Record[]): void } + | undefined; if (dp?.setFields) { dp.setFields(buildFieldsFromColumns(columns)); } + if (dp?.setRows && rowData?.length) { + dp.setRows(rowData); + } }} > {columns.map((column) => ( diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index 7d7f925ee..a3f2be7c1 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -10,6 +10,7 @@ import type { FooterItemWithCallback, ColumnLayoutItem, CellStyleCallback, + CellTooltipCallback, } from "../type/DataGrid.type"; import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"; import JSZip from "jszip"; @@ -86,110 +87,6 @@ function toNum(v: unknown): number { return parseFloat(String(v).replace(/,/g, "")) || NaN; } -/** 헤더에 정렬 화살표 표시 (CSS 방식) */ -function updateHeaderSortIndicator( - gridView: { - columnByIndex?: ( - i: number, - ) => { fieldName?: string; name?: string } | undefined; - columnByName?: ( - name: string, - ) => { fieldName?: string; name?: string } | undefined; - getColumn?: ( - i: number, - ) => { fieldName?: string; name?: string } | undefined; - }, - columns: { fieldName?: string; name: string }[], - sortDir: Record, -) { - try { - columns.forEach((col) => { - if (!col.fieldName && !col.name) return; - - // 컬럼 찾기 - let gridCol: { fieldName?: string; name?: string } | undefined; - for (let i = 0; i < 100; i++) { - const c = - gridView.columnByIndex?.(i) ?? gridView.getColumn?.(i); - if (!c) break; - if ( - c.fieldName === (col.fieldName || col.name) || - c.name === (col.fieldName || col.name) - ) { - gridCol = c; - break; - } - } - if (!gridCol) { - gridCol = gridView.columnByName?.(col.name); - } - - if (!gridCol) return; - - const fieldName = - gridCol.fieldName || gridCol.name || col.fieldName || col.name; - const direction = sortDir[fieldName] || sortDir[col.name] || "none"; - - // DOM에서 헤더 요소 찾기 - const gridEl1 = document.querySelector( - "[data-grid-id]", - ) as HTMLElement | null; - const gridEl2 = document.querySelector( - ".rg-grid", - ) as HTMLElement | null; - const gridEl = gridEl1 || gridEl2; - if (!gridEl) return; - - // 헤더 셀 찾기 (fieldName 또는 name으로) - const headerCells = gridEl.querySelectorAll( - '.rg-header-cell, [class*="header"]', - ); - let targetCell: HTMLElement | null = null; - - headerCells.forEach((cell) => { - const el = cell as HTMLElement; - const text = el.textContent?.trim(); - const colName = col.name; - const colFieldName = col.fieldName; - if ( - text && - (text.includes(colName) || - (colFieldName && text.includes(colFieldName))) - ) { - targetCell = el; - } - }); - - if (!targetCell) return; - - const headerCell = targetCell as HTMLElement; - - // 기존 화살표 제거 - const existingIndicator = headerCell.querySelector( - ".rg-sort-indicator", - ) as HTMLElement | null; - if (existingIndicator) { - existingIndicator.remove(); - } - - // 화살표 추가 - if (direction === "asc" || direction === "desc") { - const indicator = document.createElement("span"); - indicator.className = "rg-sort-indicator"; - indicator.textContent = direction === "asc" ? "▲" : "▼"; - indicator.style.marginLeft = "4px"; - indicator.style.fontSize = "10px"; - indicator.style.color = "#666"; - indicator.style.display = "inline-block"; - indicator.style.verticalAlign = "middle"; - headerCell.appendChild(indicator); - } - }); - } catch (e) { - console.warn("updateHeaderSortIndicator error:", e); - } -} - /** onSorting: mergeCells: true인 컬럼이 있으면 그룹 내 순서 유지 정렬, 없으면 RealGrid 기본 정렬 */ function createOnSortingHandler( columns: { @@ -215,12 +112,14 @@ function createOnSortingHandler( ), ]; // mergeCells: true인 컬럼 찾기 (시군구 등) - 그룹 필드로 사용 - // 시군구를 우선으로 찾고, 없으면 첫 번째 mergeCells 컬럼 사용 + // 시군구(sggNm) 우선, 없으면 행정구역명(admdstNm), 없으면 첫 번째 mergeCells 컬럼 const mergeCellCols = columns.filter( (c) => c.mergeCells === true && c.fieldName, ); const mergeCellCol = - mergeCellCols.find((c) => c.fieldName === "gugun") || mergeCellCols[0]; + mergeCellCols.find((c) => c.fieldName === "sggNm") || + mergeCellCols.find((c) => c.fieldName === "admdstNm") || + mergeCellCols[0]; const groupField = mergeCellCol?.fieldName || null; const orderByField = "displayYear"; // 고정값 또는 설정 가능하게 할 수 있음 const hasMergeCells = mergeCellCols.length > 0; @@ -251,7 +150,8 @@ function createOnSortingHandler( const col = columns.find( (c) => c.fieldName === sortField || c.name === sortField, ); - if (!col || col.sortdisable) return true; + // sortdisable 컬럼은 정렬 불가(클릭 무시) + if (!col || col.sortdisable) return false; // mergeCells: true인 컬럼이 없으면 RealGrid 기본 정렬 사용 if (!hasMergeCells || !groupField) return true; @@ -276,6 +176,7 @@ function createOnSortingHandler( if (orig && setRows) { setRows(orig); setRowData(orig); + (g as { refresh?: () => void }).refresh?.(); } sortDirRef.current[sortField] = "none"; return false; @@ -294,11 +195,13 @@ function createOnSortingHandler( ); setRows?.(sorted); setRowData(sorted); + // 데이터 변경 후 merge 재적용 + (g as { refresh?: () => void }).refresh?.(); return false; }; } -/** sortPreserveGroupOrder: 그룹 간에는 sortField로 정렬, 그룹 내에서는 원본 순서 유지. */ +/** sortPreserveGroupOrder: 그룹 간에는 sortField로 정렬, 그룹 내에서는 원본 순서 유지(구분 포함). */ function sortRowsGroupPreserving( rows: Record[], sortField: string, @@ -415,7 +318,9 @@ const DataGrid = ({ setRowData, getGridDataRef, cellStyleCallback, + cellTooltipCallback, onRowClick, + statusInfo, }: DataGridProps) => { const { columns, columnLayout } = columnInfo; const gridRef = useRef(null); @@ -424,8 +329,10 @@ const DataGrid = ({ const originalRowsRef = useRef[]>>({}); const rowDataRef = useRef[]>(rowData); const cellStyleCallbackRef = useRef(cellStyleCallback); + const cellTooltipCallbackRef = useRef(cellTooltipCallback); rowDataRef.current = rowData; cellStyleCallbackRef.current = cellStyleCallback; + cellTooltipCallbackRef.current = cellTooltipCallback; useLayoutEffect(() => { if (!cellStyleCallback || !gridRef.current?.gridView) return; @@ -542,6 +449,7 @@ const DataGrid = ({ target: "local", fileName: `${menuName}.xlsx`, footer: "hidden", + allColumns: true, // 숨김(visible: false) 컬럼도 엑셀에 포함 done: () => { // 헤더 원복 columns.forEach((col) => { @@ -624,10 +532,10 @@ const DataGrid = ({ ) { const cell = worksheet[ - XLSX.utils.encode_cell({ - r: rowIndex, - c: col, - }) + XLSX.utils.encode_cell({ + r: rowIndex, + c: col, + }) ]; if ( cell && @@ -703,7 +611,7 @@ const DataGrid = ({ const col = range.s.c + colIndex; const cell = worksheet[ - XLSX.utils.encode_cell({ r: row, c: col }) + XLSX.utils.encode_cell({ r: row, c: col }) ]; const headerName = headers[colIndex] || `Column${colIndex + 1}`; @@ -945,12 +853,14 @@ const DataGrid = ({ editable: col.editable, valueType: col.valueType, dataFormat: col.dataFormat, + ...(col.width != null && { width: col.width }), ...(col.styleName != null && { styleName: col.styleName, }), ...(col.mergeCells && { mergeRule: { criteria: "value" as const }, }), + sortable: !col.sortdisable, }; if (col.footer) { @@ -999,7 +909,7 @@ const DataGrid = ({ ) { ( column.footer[ - index + index ] as FooterItemWithCallback ).valueCallback = footerItem.valueCallback; @@ -1041,6 +951,8 @@ const DataGrid = ({ ); } }); + // valueCallback 적용 후 푸터 셀 다시 그리기 (API 합계 등 우선 표시 반영) + currentGridView.refresh?.(); }, 200); if (getGridDataRef) { @@ -1050,11 +962,11 @@ const DataGrid = ({ getGridDataRef.current = () => { const gv = gridRef.current?.gridView as | { - getDataSource?: () => { - getRowCount(): number; - getValue(r: number, f: string): unknown; - }; - } + getDataSource?: () => { + getRowCount(): number; + getValue(r: number, f: string): unknown; + }; + } | undefined; if (!gv?.getDataSource) return []; const ds = gv.getDataSource(); @@ -1083,38 +995,38 @@ const DataGrid = ({ getGridDataRef, ]); - // 행 클릭 이벤트 설정 + // 행 클릭 이벤트 + 검증 오류 셀 클릭 시 showMessage (공식 문서 방식) useEffect(() => { - if (!gridRef.current?.gridView || !onRowClick) return; + const gridView = gridRef.current?.gridView; + if (!gridView || (!onRowClick && !cellTooltipCallback)) return; - const gridView = gridRef.current.gridView; - - // onCellClicked 이벤트 핸들러 설정 gridView.onCellClicked = (grid, clickData) => { - // 데이터 셀만 처리 (헤더, 푸터 등 제외) - if ( - clickData.cellType === "data" && - clickData.itemIndex !== undefined - ) { - try { - // 행 전체 데이터 가져오기 - const row = rowData[clickData.itemIndex]; - - // 부모 컴포넌트로 데이터 전달 - onRowClick(row ?? {}); - } catch (error) { - console.error("행 데이터 가져오기 오류:", error); + if (clickData.cellType !== "data" || clickData.itemIndex === undefined) return; + try { + const row = rowDataRef.current[clickData.itemIndex]; + const cellIndex = (grid as { getCurrent?: () => { itemIndex: number; column?: string; fieldName?: string } }).getCurrent?.(); + // cellTooltipCallback 있으면 해당 셀 메시지로 showMessage (공식 문서: TooltipOptions + CellIndex) + if (cellTooltipCallbackRef.current && row && cellIndex) { + const fieldName = cellIndex.fieldName ?? cellIndex.column ?? ""; + const msg = cellTooltipCallbackRef.current({ row: row as Record, fieldName }); + if (msg) { + const colName = columns.find((c) => c.fieldName === fieldName || c.name === cellIndex.column)?.name ?? cellIndex.column ?? fieldName; + (gridView as { showMessage?: (opts: { message: string; hideDuration?: number }, idx: { itemIndex: number; column: string }) => void }).showMessage?.( + { message: msg, hideDuration: 5000 }, + { itemIndex: cellIndex.itemIndex, column: colName } + ); + } } + if (onRowClick) onRowClick((row ?? {}) as Record); + } catch (error) { + console.error("행 데이터 가져오기 오류:", error); } }; - // cleanup: 컴포넌트 언마운트 시 이벤트 핸들러 제거 return () => { - if (gridView.onCellClicked) { - gridView.onCellClicked = undefined; - } + if (gridView.onCellClicked) gridView.onCellClicked = undefined; }; - }, [onRowClick]); + }, [onRowClick, cellTooltipCallback, columns]); return (
@@ -1126,6 +1038,26 @@ const DataGrid = ({ onChange={handleFileChange} />
+ {statusInfo && +
+ 진행상황: { + statusInfo.laeSbmsnSttsCd === null || statusInfo.laeSbmsnSttsCd === '0' ? + '미작성' + : + statusInfo.laeSbmsnSttsCd === '1' ? '작성중' : statusInfo.laeSbmsnSttsCd === '2' ? '제출' + : + statusInfo.laeSbmsnSttsCd === '3' ? '승인' : '반려' + } + + 제출일: {statusInfo.laeSbmsnYmd + ? (statusInfo.laeSbmsnYmd.length === 8 && /^\d{8}$/.test(statusInfo.laeSbmsnYmd) + ? `${statusInfo.laeSbmsnYmd.slice(0, 4)}-${statusInfo.laeSbmsnYmd.slice(4, 6)}-${statusInfo.laeSbmsnYmd.slice(6, 8)}` + : statusInfo.laeSbmsnYmd) + : '미제출'} + 담당자: {statusInfo.laePicNm ? statusInfo.laePicNm : '-'} + 연락처: {statusInfo.laePicTelNo ? statusInfo.laePicTelNo : '-'} +
+ } {isExcelImport && ( + + + + + + ); +}; + +export default SubmitDailog; \ No newline at end of file diff --git a/apps/st/src/components/statistics/management/LogHistory.tsx b/apps/st/src/components/statistics/management/LogHistory.tsx new file mode 100644 index 000000000..0b45ce9fd --- /dev/null +++ b/apps/st/src/components/statistics/management/LogHistory.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { useState } from 'react'; +// import useSWR from 'swr'; + +import { + Button, + // Input, + Label, + // Select, + // SelectTrigger, + // SelectContent, + // SelectItem, + // SelectValue +} + from "@common/components"; +import DataGrid from '@/components/module/DataGrid'; +import { CodeSelect } from '@/entities/code'; + +import { LogHistoryColumn } from '@/components/util/column/management/LogHistoryColumn'; +import { useAlertStore } from "@/shared/lib"; +// import { LwpoCodeApi } from "@/entities/code"; + +// const getStateCdListFetcher = async () => { +// return LwpoCodeApi.getLwpoCodeList({ +// sort: 'regDt,desc', +// upCd: 'STITEM', +// page: 0, +// size: 100, +// }); +// } + +const LogHistory = () => { + // const { setMessage: alert } = useAlertStore((state) => state); + const [statsCd, setStatsCd] = useState(''); + + // const { data: lwpoCdList } = useSWR( + // "lwpo-code-list", + // getStateCdListFetcher, + // { + // revalidateOnFocus: false, + // revalidateOnReconnect: false, + // revalidateIfStale: false, + // dedupingInterval: 2000, + // shouldRetryOnError: false, + // onSuccess: (data) => { + // if (!data.content || data.content.length == 0) { + // alert(`시도 목록을 불러오는데 실패했습니다. + // 새로고침 후 다시 시도해주세요.`); + // } + // }, + // onError: () => { + // alert(`시도 목록을 불러오는데 실패했습니다. + // 새로고침 후 다시 시도해주세요.`); + // }, + // } + // ); + + return ( +
+
+
+
+
+ + {/* */} + +
+
+ + +
+
+
+
+ {}} + /> +
+ ); +}; + +export default LogHistory; \ No newline at end of file diff --git a/apps/st/src/components/statistics/review/PumpingStationRvw.tsx b/apps/st/src/components/statistics/review/PumpingStationRvw.tsx index 8a9df541a..3fc34a034 100644 --- a/apps/st/src/components/statistics/review/PumpingStationRvw.tsx +++ b/apps/st/src/components/statistics/review/PumpingStationRvw.tsx @@ -1,26 +1,82 @@ -import { useState } from "react"; - -import { DataGrid } from "@rsone/components"; +import { useState, useMemo } from "react"; +import DataGrid from "@/components/module/DataGrid"; import SearchForm from "@/components/module/SearchForm"; import SaveButton from "@/components/button/SaveButton"; import ApproveButton from "@/components/button/ApproveButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReturnButton from "@/components/button/ReturnButton"; +import { LoadingWithAnimation } from "@common/components"; +import { useAlertStore } from "@/shared/lib"; import { PumpingStationInfoList } from "@/components/util/column/review/PumpingStationRvwColumn"; +import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; +import { toGridRows } from "@/components/util/grid/gridFunction"; -// 하수도 통계 관리 05.펌프장 +// 하수도 통계 검토 05.중계펌프장 const PumpingStationRvw = () => { + const { setMessage: alert } = useAlertStore((state) => state); const [rowData, setRowData] = useState[]>([]); + const [isLoading, setIsLoading] = useState(false); - const approveFunc = () => { console.log('approveFunc'); }; - const saveFunc = () => { console.log('submit'); }; - const reviewOpinionFunc = () => { console.log('reviewOpinionFunc'); }; - const returnFunc = () => { console.log('returnFunc'); }; + const columnInfo = useMemo(() => { + const { columns, columnLayout, fixedColumns, rowStartNum } = PumpingStationInfoList; + return { + columnLayout, + fixedColumns, + rowStartNum, + columns: columns.map((col) => ({ + ...col, + editable: false as const, // 검토 화면이므로 읽기 전용 + styleName: col.styleName ?? ("rg-cell-readonly" as const), + })), + }; + }, []); + + const handleReset = () => { + setRowData([]); + }; + + const handleSearch = async (values: SearchFormValues) => { + if (values.sido === '' || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + + try { + setIsLoading(true); + + const response = await statisticsManagementApi.getMediationPumpStationList({ + sido: values.sido, + gugun: values.gugun === 'ALL' ? '' : values.gugun, + bsnmCd: values.bsnmCd, + yr: values.year, + bscFctNm: values.fctName, // 시설명 검색 + }); + + if (response.status === 'success' && response.data) { + setRowData(toGridRows(response.data)); + } else { + setRowData([]); + alert(response.message || "데이터가 없습니다."); + } + } catch (error) { + console.error("중계펌프장 조회 중 에러 발생:", error); + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + }; + + const approveFunc = () => console.log('approveFunc'); + const saveFunc = () => console.log('submit'); + const reviewOpinionFunc = () => console.log('reviewOpinionFunc'); + const returnFunc = () => console.log('returnFunc'); return ( - <> - +
+ +
@@ -29,8 +85,24 @@ const PumpingStationRvw = () => {
- - + + {isLoading && ( +
+
+ +
+
+ )} + + +
); }; diff --git a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx index 03549083e..d8cdc7d0e 100644 --- a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx @@ -1,51 +1,161 @@ -import { useState } from "react"; - -import { TabDataGrid } from "@rsone/components"; +import { useState, useMemo, useRef } from "react"; +import TabDataGrid from "@/components/module/TabDataGrid"; import SearchForm from "@/components/module/SearchForm"; import SaveButton from "@/components/button/SaveButton"; import ApproveButton from "@/components/button/ApproveButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReturnButton from "@/components/button/ReturnButton"; +import { LoadingWithAnimation } from "@common/components"; +import { useAlertStore } from "@/shared/lib"; +import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn"; +import type { ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import type { SewerPipeMtcRequestParams } from "@/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface"; -// 하수도 통계 관리 03. 하수관로 유지 관리 + +// 하수도 통계 검토 03. 하수관로 유지 관리 const SewagePipeMaintainRvw = () => { + const { setMessage: alert } = useAlertStore((state) => state); const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); + const [isLoading, setIsLoading] = useState(false); + const [activeTab, setActiveTab] = useState('ST00030001'); - const approveFunc = () => { - console.log('approveFunc'); - // 승인 로직 + const searchParamRef = useRef(null); + + const columnInfo = useMemo(() => { + return SewagePipeMaintainColumnInfoList.map((tab: ColumnInfoList) => ({ + ...tab, + columns: (tab.columns ?? []).map((col: ColumnConfig) => ({ + ...col, + editable: false as const, + styleName: col.styleName ?? ("rg-cell-readonly" as const), + })), + })); + }, []); + + const handleReset = () => { + setRowDataMap(new Map()); + setActiveTab('ST00030001'); + searchParamRef.current = null; }; - const saveFunc = () => { - console.log('submit'); - // 저장 로직 + const handleSearch = async (values: SearchFormValues) => { + if (!values.sido || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + + setActiveTab('ST00030001'); + setRowDataMap(new Map()); + + try { + setIsLoading(true); + + const searchParam: SewerPipeMtcRequestParams = { + statsYr: values.year, + sewerBzmnCd: values.bsnmCd, + ctpvNm: values.sido, + sggNm: values.gugun === 'ALL' ? '' : values.gugun + }; + + searchParamRef.current = searchParam; + const response = await statisticsManagementApi.getSewerPipeMtcList(searchParam); + + if (response?.status === 'success' && response.data) { + const newRowDataMap = new Map[]>(); + + const listData = Array.isArray(response.data) + ? response.data + : (response.data.mtcStatusList || []); + + newRowDataMap.set('ST00030001', toGridRows(listData as object[])); + setRowDataMap(newRowDataMap); + } else { + alert(response?.message || "데이터가 없습니다."); + } + } catch { + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } }; - const reviewOpinionFunc = () => { - console.log('reviewOpinionFunc'); - // 검토의견 작성 - }; + const handleActiveTabChange = async (tabKey: string) => { + setActiveTab(tabKey); - const returnFunc = () => { - console.log('returnFunc'); - // 반려 + const existingAData = rowDataMap.get('ST00030001'); + 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 === 'ST00030001') { + response = await statisticsManagementApi.getSewerPipeMtcList(searchParamRef.current); + } else if (tabKey === 'ST00030002') { + // response = await statisticsManagementApi.getSewerPipeDrdgList(searchParamRef.current); + } + + if (response?.status === 'success' && response.data) { + setRowDataMap(prev => { + const next = new Map(prev); + + const listData = Array.isArray(response.data) + ? response.data + : (response.data.mtcStatusList || []); + + next.set(tabKey, toGridRows(listData as object[])); + return next; + }); + } else { + alert(response?.message || "데이터가 없습니다."); + } + } catch { + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + } }; return ( - <> - +
+ +
- - - - + { }} /> + { }} /> + { }} /> + { }} />
- - + + {isLoading && ( +
+
+ +
+
+ )} + + +
); }; diff --git a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx index 34fe00910..fab4d2cbc 100644 --- a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx @@ -13,15 +13,17 @@ import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/Sew import type { ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import type { SewagePipeReviewRequestParams } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface"; + // 하수도 통계 검토 02.하수관로 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 [activeTab, setActiveTab] = useState('ST00020001'); - const searchParamRef = useRef(null); + const searchParamRef = useRef(null); const columnInfo = useMemo(() => { return SewagePipeColumnInfoListRvw.map((tab: ColumnInfoList) => ({ @@ -36,7 +38,7 @@ const SewagePipeRvw = () => { const handleReset = () => { setRowDataMap(new Map()); - setActiveTab('a'); + setActiveTab('ST00020001'); }; const handleSearch = async (values: SearchFormValues) => { @@ -45,18 +47,18 @@ const SewagePipeRvw = () => { return; } - setActiveTab('a'); + setActiveTab('ST00020001'); setRowDataMap(new Map()); try { setIsLoading(true); - const searchParam = { - sido: values.sido, - gugun: values.gugun === 'ALL' ? '' : values.gugun, - bsnmCd: values.bsnmCd, - yr: values.year, + const searchParam: SewagePipeReviewRequestParams = { + statsYr: values.year, + sewerBzmnCd: values.bsnmCd, + ctpvNm: values.sido, + sggNm: values.gugun === 'ALL' ? '' : values.gugun }; searchParamRef.current = searchParam; @@ -64,12 +66,12 @@ const SewagePipeRvw = () => { if (response?.status === 'success' && response.data) { const newRowDataMap = new Map[]>(); - newRowDataMap.set('a', toGridRows(response.data as object[])); + newRowDataMap.set('ST00020001', toGridRows(response.data as object[])); setRowDataMap(newRowDataMap); } else { alert(response?.message || "데이터가 없습니다."); } - } catch (error) { + } catch { alert('데이터 조회 중 오류가 발생했습니다.'); } finally { setIsLoading(false); @@ -79,7 +81,7 @@ const SewagePipeRvw = () => { const handleActiveTabChange = async (tabKey: string) => { setActiveTab(tabKey); - const existingAData = rowDataMap.get('a'); + const existingAData = rowDataMap.get('ST00020001'); const activeTabData = rowDataMap.get(tabKey); const hasAData = Array.isArray(existingAData) && existingAData.length > 0; @@ -92,11 +94,17 @@ const SewagePipeRvw = () => { let response; - if (tabKey === 'b') { + if (tabKey === 'ST00020002') { response = await statisticsManagementApi.getSewagePipeFacilityList(searchParamRef.current); } + // }else if(tabKey === 'ST00020003'){ + // response = await statisticsManagementApi.getSewagePipeTypeList(searchParamRef.current); + // }else if(tabKey === 'ST00020004'){ + // response = await statisticsManagementApi.getSewagePipeDiamList(searchParamRef.current); + // } if (response?.status === 'success' && response.data) { + setRowDataMap(prev => { const next = new Map(prev); next.set(tabKey, toGridRows(response.data as object[])); diff --git a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx index a2b5e8de7..13303f6c1 100644 --- a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx +++ b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx @@ -1,52 +1,150 @@ -import { useState } from "react"; - -import { TabDataGrid } from "@rsone/components"; +import { useState, useMemo, useRef } from "react"; +import TabDataGrid from "@/components/module/TabDataGrid"; import SearchForm from "@/components/module/SearchForm"; import SaveButton from "@/components/button/SaveButton"; import ApproveButton from "@/components/button/ApproveButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReturnButton from "@/components/button/ReturnButton"; +import { LoadingWithAnimation } from "@common/components"; +import { useAlertStore } from "@/shared/lib"; +import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { SewageReservoirPumpInfoListRvw } from "@/components/util/column/review/SewageReservoirPumpRvwColumn"; +import type { ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; +import { toGridRows } from "@/components/util/grid/gridFunction"; -// 하수도 통계 관리 04. 하수저류 시설 및 빗물 펌프장 +/** + * 하수도 통계 검토 04. 하수저류 시설 및 빗물 펌프장 + * Tab A: 하수저류시설 + * Tab B: 빗물펌프장 + */ const SewageReservoirPumpRvw = () => { + const { setMessage: alert } = useAlertStore((state) => state); const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); + const [isLoading, setIsLoading] = useState(false); + const [activeTab, setActiveTab] = useState('ST00040001'); - const approveFunc = () => { - console.log('approveFunc'); - // 승인 로직 + const searchParamRef = useRef(null); + + const columnInfo = useMemo(() => { + return SewageReservoirPumpInfoListRvw.map((tab: ColumnInfoList) => ({ + ...tab, + columns: (tab.columns ?? []).map((col: ColumnConfig) => ({ + ...col, + editable: false as const, + styleName: col.styleName ?? ("rg-cell-readonly" as const), + })), + })); + }, []); + + const handleReset = () => { + setRowDataMap(new Map()); + setActiveTab('ST00040001'); + searchParamRef.current = null; }; - const saveFunc = () => { - console.log('submit'); - // 저장 로직 + const handleSearch = async (values: SearchFormValues) => { + if (!values.sido || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + + setActiveTab('ST00040001'); + setRowDataMap(new Map()); + + try { + setIsLoading(true); + + const searchParam = { + sido: values.sido, + gugun: values.gugun === 'ALL' ? '' : values.gugun, + bsnmCd: values.bsnmCd, + yr: values.year, + }; + + searchParamRef.current = searchParam; + + //하수저류시설 (Tab A) + const response = await statisticsManagementApi.getSewerReservoirList(searchParam); + + if (response?.status === 'success' && response.data) { + const newRowDataMap = new Map[]>(); + newRowDataMap.set('ST00040001', toGridRows(response.data as object[])); + setRowDataMap(newRowDataMap); + } else { + alert(response?.message || "데이터가 없습니다."); + } + } catch { + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } }; - const reviewOpinionFunc = () => { - console.log('reviewOpinionFunc'); - // 검토의견 작성 - }; + const handleActiveTabChange = async (tabKey: string) => { + setActiveTab(tabKey); - const returnFunc = () => { - console.log('returnFunc'); - // 반려 + const activeTabData = rowDataMap.get(tabKey); + const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0; + + if (searchParamRef.current && !hasActiveTabData) { + try { + setIsLoading(true); + let response; + + if (tabKey === 'ST00040002') { + response = await statisticsManagementApi.getPumpStationList(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 if (response) { + alert(response?.message || "데이터가 없습니다."); + } + } catch { + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + } }; return ( - <> - +
+ +
- - - - + { /* 검토의견 로직 */ }} /> + { /* 승인 로직 */ }} /> + { /* 반려 로직 */ }} /> + { /* 저장 로직 */ }} />
- - + + {isLoading && ( +
+
+ +
+
+ )} + + +
); }; -export default SewageReservoirPumpRvw; +export default SewageReservoirPumpRvw; \ No newline at end of file diff --git a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx index 8ad001c4c..18a0443cc 100644 --- a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx +++ b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx @@ -5,7 +5,7 @@ import SaveButton from "@/components/button/SaveButton"; import ApproveButton from "@/components/button/ApproveButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReturnButton from "@/components/button/ReturnButton"; -import { LoadingWithAnimation } from "@common/components"; +import { LoadingWithAnimation } from "@common/components"; import { useAlertStore } from "@/shared/lib"; import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn"; @@ -17,8 +17,8 @@ import { toGridRows } from "@/components/util/grid/gridFunction"; // 하수도 통계 검토 01.하수도 보급률 const SeweragePenetrationRateRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); - const [rowData, setRowData] = useState([]); - const [isLoading, setIsLoading] = useState(false); + const [rowData, setRowData] = useState[]>([]); + const [isLoading, setIsLoading] = useState(false); const columnInfo = useMemo(() => { const { columns, columnLayout, fixedColumns, rowStartNum } = SeweragePenetrationRateInfoList; @@ -39,6 +39,7 @@ const SeweragePenetrationRateRvw = () => { }; const handleSearch = async (values: SearchFormValues) => { + if (values.sido === '' || values.sido === 'ALL') { alert('시도를 선택해주세요.'); return; @@ -48,17 +49,18 @@ const SeweragePenetrationRateRvw = () => { setIsLoading(true); const response = await statisticsManagementApi.getSewerageSupplyRateReviewList({ - sido: values.sido, - gugun: values.gugun === 'ALL' ? '' : values.gugun, - bsnmCd: values.bsnmCd, - yr: values.year, + ctpvNm: values.sido, + sggNm: values.gugun === 'ALL' ? '' : values.gugun, + sewerBzmnCd: values.bsnmCd, + statsYr: values.year, }); if (response.status === 'success' && response.data) { const rawList = response.data.sewerageSupplyRateList; const convertedData: SewerageSupplyRateReviewItem[] = rawList.map((item) => ({ ...item, - speclCntrplnAr: !item.speclCntrplnAr ? '해당없음' : item.speclCntrplnAr, + + spcCtpnRgnCn: !item.spcCtpnRgnCn ? '해당없음' : item.spcCtpnRgnCn, })); setRowData(toGridRows(convertedData)); @@ -70,7 +72,7 @@ const SeweragePenetrationRateRvw = () => { console.error("조회 중 에러 발생:", error); alert('데이터 조회 중 오류가 발생했습니다.'); } finally { - setIsLoading(false); + setIsLoading(false); } }; @@ -80,9 +82,9 @@ const SeweragePenetrationRateRvw = () => { const returnFunc = () => console.log('returnFunc'); return ( -
+
- +
diff --git a/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx b/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx index 2c11f7abc..8fdd74d4f 100644 --- a/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx +++ b/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx @@ -11,12 +11,13 @@ import { useAlertStore } from "@/shared/lib"; import { StatsInputStatusInfoList } from "@/components/util/column/review/StatsInputStatusRvwColumn"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import type { StatsInputStatusReviewItem } from "@/entities/statistics/model/statistics-input-review-interface"; -import { toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; // 하수도 통계 검토 00. 통계 입력 현황 const StatsInputStatusRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); - const [rowData, setRowData] = useState([]); + const [rowData, setRowData] = useState[]>([]); const [isLoading, setIsLoading] = useState(false); const columnInfo = useMemo(() => { @@ -34,7 +35,7 @@ const StatsInputStatusRvw = () => { setRowData([]); }; - const handleSearch = async (values: any) => { + const handleSearch = async (values: SearchFormValues) => { if (!values.sido || values.sido === 'ALL') { alert('시도를 선택해주세요.'); return; @@ -46,14 +47,13 @@ const StatsInputStatusRvw = () => { const response = await statisticsManagementApi.getStatsInputStatusList({ sido: values.sido, gugun: values.gugun === 'ALL' ? '' : values.gugun, - yr: values.year || values.yr, + yr: values.year, bsnmCd: values.bsnmCd }); if (response.status === 'success' && response.data) { - const rawList = response.data.sewerageSupplyRateList || response.data; - - const convertedData: StatsInputStatusReviewItem[] = rawList.map((item: any) => ({ + const rawList = response.data.sewerageSupplyRateList ?? []; + const convertedData: StatsInputStatusReviewItem[] = rawList.map((item: StatsInputStatusReviewItem) => ({ ...item, status: item.status || '미확인', })); diff --git a/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx b/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx new file mode 100644 index 000000000..0bd348123 --- /dev/null +++ b/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { useState } from 'react'; + +import { CodeSelect } from '@/entities/code'; +import { + Button, + Label, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; + +const GeneralVerificationManagement = () => { + const [statsCd, setStatsCd] = useState(''); + + return ( +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+ + + No + 검증항목 + 등록자 + 등록일 + 사용여부 + + + + + 1 + + + 인구수검증 + + + 관리자 + + + 2026-02-09 + + + Y + + + +
+
+
+ +
+
+
+ ); +}; + +export default GeneralVerificationManagement; \ No newline at end of file diff --git a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx index 81fa83b2a..0f44b8e4a 100644 --- a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx +++ b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx @@ -1,482 +1,748 @@ "use client"; -import { useState } from "react"; -// import { DataGrid } from "@rsone/components"; -import DataGrid from "@/components/module/DataGrid"; -import SearchForm from "@/components/module/SearchForm"; +import { useEffect, useMemo, useState } from "react"; + +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + getCoreRowModel, + getPaginationRowModel, + useReactTable, +} from "@tanstack/react-table"; +import type { AxiosError } from "axios"; +import dayjs from "dayjs"; + +import { ENDPOINT } from "@/shared/config"; +import { useAlertStore } from "@/shared/lib"; +import type { ApiErrorResponse } from "@/shared/model"; +import { CodeSelect } from "@/entities/code"; +import { analysisApi } from "@/entities/statistics/api/analysis-api"; +import { analysisHistoryApi } from "@/entities/statistics/api/analysis-history-api"; +import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service"; +import type { + StAnlsMstResponse, + StAnlsMstSearchParams, + StAnlsOutlierListResponse, +} from "@/entities/statistics/model/analysis-history-interface"; +import type { + AnalysisRequestPayload, + AnalysisResponseDto, +} from "@/entities/statistics/model/analysis-interface"; +import { verificationHistoryColumns } from "@/entities/statistics/model/verification-history-columns"; +import type { VerificationTargetCodeResponse } from "@/entities/statistics/model/verification-target-management-interface"; import { Button, + Checkbox, + DataTable, + DateRangePicker, + Dialog, + DialogContent, + DialogHeader, + DialogTitle, Input, Label, + LoadingWithAnimation, + RadioGroup, + RadioGroupItem, Select, - SelectTrigger, SelectContent, SelectItem, + SelectTrigger, SelectValue, - Checkbox, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, } from "@common/components"; -import { verificationHistoryColumnInfo } from "@/entities/statistics/model/verification-history-column"; -import { verificationHistoryMockData } from "@/entities/statistics/model/verification-history-mock"; -interface VerificationFormValues { +import HbVerificationDetail from "./detail/HbVerificationDetail"; + +type DateRangeValue = { + from: Date; + to?: Date; +}; + +type VerifyType = "ALL" | "SELECT"; + +interface VerificationFormState { mu: string; percentile: string; a: string; c: string; verificationYear: string; - checkboxOptions: { - sidoDistribution: boolean; - countyDistribution: boolean; - countyDistributionUserManagement: boolean; - lowerDistribution: boolean; - sidoServiceAvailability: boolean; - individualService: boolean; - legalServiceUsage: boolean; - excessUse: boolean; - lowerOrgStatistics: boolean; - studentStatistics: boolean; - developmentStatistics: boolean; - lowerManagementEnterprise: boolean; - }; +} + +interface OutlierDialogState { + open: boolean; + anlsSn: number | null; +} + +function getDefaultHistoryDateRange(): DateRangeValue { + const to = dayjs().endOf("day").toDate(); + const from = dayjs().subtract(7, "day").startOf("day").toDate(); + return { from, to }; +} + +function getVerificationYearOptions(): string[] { + const currentYear = dayjs().year(); + const startYear = 2017; + const endYear = currentYear - 1; + if (endYear < startYear) return []; + return Array.from({ length: endYear - startYear + 1 }, (_, index) => + String(startYear + index), + ); +} + +function toNumber(value: string): number { + const parsed = Number(value); + return Number.isNaN(parsed) ? 0 : parsed; } const HbStatisticsVerification = () => { - const [verificationHistoryRowData, setVerificationHistoryRowData] = - useState[]>( - verificationHistoryMockData, - ); + const { setMessage: alert } = useAlertStore((state) => state); + const queryClient = useQueryClient(); - const [formValues, setFormValues] = useState({ + const yearOptions = useMemo(() => getVerificationYearOptions(), []); + const defaultYear: string = + yearOptions.length > 0 + ? (yearOptions[yearOptions.length - 1] ?? "") + : ""; + + const [verifyType, setVerifyType] = useState("ALL"); + const [formState, setFormState] = useState({ mu: "0", percentile: "0", a: "0", c: "0", - verificationYear: "2025", - checkboxOptions: { - sidoDistribution: false, - countyDistribution: false, - countyDistributionUserManagement: false, - lowerDistribution: false, - sidoServiceAvailability: false, - individualService: false, - legalServiceUsage: false, - excessUse: false, - lowerOrgStatistics: false, - studentStatistics: false, - developmentStatistics: false, - lowerManagementEnterprise: false, + verificationYear: defaultYear, + }); + const [selectedTargets, setSelectedTargets] = useState([]); + const [outlierDialog, setOutlierDialog] = useState({ + open: false, + anlsSn: null, + }); + const [selectedStatsCd, setSelectedStatsCd] = useState(""); + const [selectedAnlsSn, setSelectedAnlsSn] = useState(null); + + const defaultHistoryDateRange = useMemo( + () => getDefaultHistoryDateRange(), + [], + ); + const [historyDateRange, setHistoryDateRange] = useState< + DateRangeValue | undefined + >(defaultHistoryDateRange); + const [appliedHistoryDateRange, setAppliedHistoryDateRange] = useState< + DateRangeValue | undefined + >(defaultHistoryDateRange); + + const [historyPagination, setHistoryPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + const { data: verificationTargetCodes } = useQuery< + VerificationTargetCodeResponse[] + >({ + queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG, "codes"], + queryFn: () => + verificationTargetManagementApi.getVerificationTargetCodes(), + }); + + const filteredVerificationTargetCodes = useMemo(() => { + return verificationTargetCodes?.filter((item) => + selectedStatsCd + ? item.vrfcTrgtSn.startsWith(selectedStatsCd) + : true, + ); + }, [verificationTargetCodes, selectedStatsCd]); + + const allVerificationTargetIds = useMemo( + () => + (filteredVerificationTargetCodes ?? []).map( + (item) => item.vrfcTrgtSn, + ), + [filteredVerificationTargetCodes], + ); + + useEffect(() => { + if (!allVerificationTargetIds.length) return; + setSelectedTargets(allVerificationTargetIds); + }, [allVerificationTargetIds]); + + const { data: analysisHistory } = useQuery({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, + historyPagination, + appliedHistoryDateRange, + ], + queryFn: () => { + const params: StAnlsMstSearchParams = { + page: historyPagination.pageIndex, + size: historyPagination.pageSize, + }; + + if (appliedHistoryDateRange?.from) { + params.verifyDtStart = dayjs( + appliedHistoryDateRange.from, + ).format("YYYY-MM-DD"); + } + + if (appliedHistoryDateRange?.to || appliedHistoryDateRange?.from) { + const to = + appliedHistoryDateRange.to ?? appliedHistoryDateRange.from!; + params.verifyDtEnd = dayjs(to).format("YYYY-MM-DD"); + } + + return analysisHistoryApi.getAnalysisHistoryList(params); + }, + refetchInterval: (query) => { + const data = query.state.data; + if (!data?.content?.length) return false; + const hasProcessing = (data.content as StAnlsMstResponse[]).some( + (item) => item.anlsSttsCd === "PROCESSING", + ); + return hasProcessing ? 3000 : false; }, }); - const handleCheckboxChange = ( - key: keyof VerificationFormValues["checkboxOptions"], - ) => { - setFormValues((prev) => ({ - ...prev, - checkboxOptions: { - ...prev.checkboxOptions, - [key]: !prev.checkboxOptions[key], - }, - })); - }; + const historyRowData: StAnlsMstResponse[] = (analysisHistory?.content ?? + []) as StAnlsMstResponse[]; - const handleInputChange = ( - key: keyof Omit, + const table = useReactTable({ + data: historyRowData, + columns: verificationHistoryColumns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + manualPagination: true, + pageCount: analysisHistory?.totalPages ?? 0, + state: { + pagination: historyPagination, + }, + onPaginationChange: (updater) => { + setHistoryPagination((prev) => + typeof updater === "function" ? updater(prev) : updater, + ); + }, + meta: { + onOutlierCellClick: (row: StAnlsMstResponse) => { + setOutlierDialog({ open: true, anlsSn: row.anlsSn }); + }, + }, + }); + + const { data: outlierList, isLoading: isLoadingOutlier } = useQuery({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, + "outlier", + outlierDialog.anlsSn, + ], + queryFn: async () => { + if (!outlierDialog.anlsSn) return []; + const response = await analysisHistoryApi.getOutlierList( + outlierDialog.anlsSn, + ); + return response; + }, + enabled: outlierDialog.open && !!outlierDialog.anlsSn, + }); + + const analysisMutation = useMutation< + AnalysisResponseDto, + AxiosError, + AnalysisRequestPayload + >({ + mutationFn: async (payload) => analysisApi.analyze(payload), + onSuccess: () => { + alert("검증을 요청했습니다."); + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST], + }); + }, + onError: (error) => { + alert( + error.response?.data?.message ?? + "검증 실행 중 오류가 발생했습니다.", + ); + }, + }); + + function handleFormChange( + key: Key, value: string, - ) => { - setFormValues((prev) => ({ + ) { + setFormState((prev) => ({ ...prev, [key]: value, })); - }; + } - const handleSubmit = () => { - console.log("검증 실행:", formValues); - }; + function handleTargetToggle(statsCd: string) { + setSelectedTargets((prev) => + prev.includes(statsCd) + ? prev.filter((cd) => cd !== statsCd) + : [...prev, statsCd], + ); + } - const handleReset = () => { - console.log("초기화"); - }; + async function handleSubmit() { + if (!formState.verificationYear) { + alert("검증년도를 선택해 주세요."); + return; + } + + if (verifyType === "SELECT" && selectedTargets.length === 0) { + alert("검증 대상을 1개 이상 선택해 주세요."); + return; + } + + const yearNew = toNumber(formState.verificationYear); + const payload: AnalysisRequestPayload = { + dataset: + verifyType === "ALL" + ? allVerificationTargetIds.length > 0 + ? allVerificationTargetIds.join(",") + : undefined + : selectedTargets.length > 0 + ? selectedTargets.join(",") + : undefined, + yearNew, + yearOld: yearNew - 1, + mu: toNumber(formState.mu), + percentile: toNumber(formState.percentile), + a: toNumber(formState.a), + c: toNumber(formState.c), + vrfcMthdCd: "HB", + allVerifyYn: verifyType === "ALL" ? "Y" : "N", + }; + + analysisMutation.mutate(payload); + } + + function handleReset() { + setFormState({ + mu: "0", + percentile: "0", + a: "0", + c: "0", + verificationYear: defaultYear, + }); + setSelectedTargets([]); + } + + function handleHistoryRowClick(row: StAnlsMstResponse) { + const anlsSn = Number(row.anlsSn ?? 0); + if (!anlsSn) return; + setSelectedAnlsSn(anlsSn); + } + + function applyHistoryDateSearch(nextRange: DateRangeValue | undefined) { + setAppliedHistoryDateRange(nextRange); + setHistoryPagination((prev) => ({ + ...prev, + pageIndex: 0, + })); + } + + function handleHistoryDateSearch() { + applyHistoryDateSearch(historyDateRange); + } + + function handleHistoryDateReset() { + const resetRange = getDefaultHistoryDateRange(); + setHistoryDateRange(resetRange); + applyHistoryDateSearch(resetRange); + } + + function closeOutlierDialog() { + setOutlierDialog({ open: false, anlsSn: null }); + } return ( -
-
- {/* 통계 검증 섹션 */} -
-

- 통계 검증 -

- -
- {/* 왼쪽: 검증 변수 */} -
-

- 검증 변수 -

- -
-
- - - handleInputChange( - "mu", - e.target.value, - ) - } - className="flex-1" - /> -
- -
- - - handleInputChange( - "percentile", - e.target.value, - ) - } - className="flex-1" - /> -
- -
- - - handleInputChange( - "a", - e.target.value, - ) - } - className="flex-1" - /> -
- -
- - - handleInputChange( - "c", - e.target.value, - ) - } - className="flex-1" - /> -
- -
-
-
+ + + + 이상건수 상세 + + {isLoadingOutlier && ( +
+
+ +
+
+ )} + {!isLoadingOutlier && (outlierList?.length ?? 0) === 0 && ( +

+ 표시할 이상치 데이터가 없습니다. +

+ )} + {!isLoadingOutlier && (outlierList?.length ?? 0) > 0 && ( +
+ {outlierList?.map( + (item: StAnlsOutlierListResponse) => ( + + + + + {item.vrfcTrgtNm} + + + + + 시도 + + + 시군구 + + + 행정구역 + + + 시설명 + + + 입력값 + + + + + {item.outlierDataList.map( + (data) => ( + + + {data.sido ?? "-"} + + + {data.gugun ?? "-"} + + + {data.adstDong ?? + "-"} + + + {/* 시설명: DTO에 해당 필드 없음 */} + - + + + {data.cryrVl != null + ? String( + data.cryrVl, + ) + : "-"} + + + ), + )} + +
+ ), + )} +
+ )} +
+
+ ); }; diff --git a/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx b/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx new file mode 100644 index 000000000..11eb6c8fd --- /dev/null +++ b/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx @@ -0,0 +1,87 @@ +import React, { useState } from "react"; + +import { Button } from "@common/components"; + +import AreaDIndicators from "./Indicators"; +import AreaBRegionalGrid from "./RegionalGrid"; +import AreaCScatterChart from "./ScatterChart"; +import AreaAVerificationList from "./VerificationList"; + +interface HbVerificationDetailProps { + anlsSn: number; + onBack: () => void; +} + +const HbVerificationDetail: React.FC = ({ + anlsSn, + onBack, +}) => { + const [selectedVrfcTrgtSn, setSelectedVrfcTrgtSn] = useState( + null, + ); + const [selectedSido, setSelectedSido] = useState(null); + + return ( +
+
+ +

+ 검증 상세 +

+
+ +
+
+ +
+ +
+

+ 검증 결과 상세 +

+
+
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+ ); +}; + +export default HbVerificationDetail; diff --git a/apps/st/src/components/statistics/verification/detail/Indicators.tsx b/apps/st/src/components/statistics/verification/detail/Indicators.tsx new file mode 100644 index 000000000..eaabf7849 --- /dev/null +++ b/apps/st/src/components/statistics/verification/detail/Indicators.tsx @@ -0,0 +1,86 @@ +import React from "react"; + +import { useQuery } from "@tanstack/react-query"; + +import { ENDPOINT } from "@/shared/config"; +import { stAnlsRsltApi } from "@/entities/statistics/api/statistics-analysis-result-api"; + +interface IndicatorsProps { + anlsSn: number; + vrfcTrgtSn: string | null; +} + +const Indicators: React.FC = ({ anlsSn, vrfcTrgtSn }) => { + const { data: chartData, isLoading } = useQuery({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT, + "chart", + anlsSn, + vrfcTrgtSn, + ], + queryFn: () => stAnlsRsltApi.getChartData(anlsSn, Number(vrfcTrgtSn)), + enabled: !!anlsSn && !!vrfcTrgtSn, + }); + + const dtl = chartData?.dtl; + + if (!vrfcTrgtSn) { + return ( +
+ 항목을 선택해주세요 +
+ ); + } + + if (isLoading) { + return ( +
+ 지표 데이터를 불러오는 중... +
+ ); + } + + const IndicatorItem = ({ + label, + value, + }: { + label: string; + value: number | null | undefined; + }) => ( +
+

+ {label} +

+
+ {value != null ? Number(value).toFixed(4) : "-"} +
+
+ ); + + return ( +
+ + + + + + + + +
+ ); +}; + +export default Indicators; diff --git a/apps/st/src/components/statistics/verification/detail/RegionalGrid.tsx b/apps/st/src/components/statistics/verification/detail/RegionalGrid.tsx new file mode 100644 index 000000000..940d328d9 --- /dev/null +++ b/apps/st/src/components/statistics/verification/detail/RegionalGrid.tsx @@ -0,0 +1,269 @@ +import React, { useMemo, useState } from "react"; + +import { useQuery } from "@tanstack/react-query"; +import { + type ColumnDef, + getCoreRowModel, + getPaginationRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { ENDPOINT } from "@/shared/config"; +import { CodeApi } from "@/entities/code/api/code-service"; +import { stAnlsRsltApi } from "@/entities/statistics/api/statistics-analysis-result-api"; +import type { StAnlsRsltResponse } from "@/entities/statistics/model/statistics-analysis-result-interface"; +import { + Button, + DataTable, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@common/components"; + +interface RegionalGridProps { + anlsSn: number; + vrfcTrgtSn: string | null; + selectedSido: string | null; + onSidoSelect: (sido: string | null) => void; +} + +const RegionalGrid: React.FC = ({ + anlsSn, + vrfcTrgtSn, + selectedSido, + onSidoSelect, +}) => { + const [selectedGugun, setSelectedGugun] = useState("ALL"); + const [selectedStatus, setSelectedStatus] = useState("ALL"); + + const { data: regionCodes } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CODE, "LAESECD"], + queryFn: () => CodeApi.getCode("LAESECD"), + }); + + const sidoOptions = useMemo(() => { + if (!regionCodes) return []; + return regionCodes.filter( + (code) => code.lwpoCd.startsWith("G3") && code.lwpoCd.length === 4, + ); + }, [regionCodes]); + + const gugunOptions = useMemo(() => { + if (!regionCodes || !selectedSido || selectedSido === "ALL") return []; + + const sidoPrefix = selectedSido; + return regionCodes.filter( + (code) => + code.lwpoCd.startsWith(sidoPrefix) && code.lwpoCd.length === 7, + ); + }, [regionCodes, selectedSido]); + + const { + data: resultData, + isLoading, + refetch, + } = useQuery({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT, + "list", + anlsSn, + vrfcTrgtSn, + ], + queryFn: () => + stAnlsRsltApi.getResultList({ + anlsSn, + vrfcTrgtSn: Number(vrfcTrgtSn), + page: 0, + size: 1000, + }), + enabled: false, + }); + + const rowData = useMemo(() => { + let data = (resultData?.content ?? []) as StAnlsRsltResponse[]; + + if (selectedSido && selectedSido !== "ALL") { + const sidoName = sidoOptions.find( + (opt) => opt.lwpoCd === selectedSido, + )?.lwpoCdNm; + if (sidoName) { + data = data.filter((item) => item.sido === sidoName); + } + } + + if (selectedGugun && selectedGugun !== "ALL") { + const gugunName = gugunOptions.find( + (opt) => opt.lwpoCd === selectedGugun, + )?.lwpoCdNm; + if (gugunName) { + data = data.filter((item) => item.gugun === gugunName); + } + } + + if (selectedStatus && selectedStatus !== "ALL") { + const statusTarget = selectedStatus === "NORMAL" ? "y" : "n"; + data = data.filter( + (item) => item.otlrYn.toLowerCase() === statusTarget, + ); + } + + return data; + }, [ + resultData, + selectedSido, + selectedGugun, + selectedStatus, + sidoOptions, + gugunOptions, + ]); + + const columns: ColumnDef[] = useMemo( + () => [ + { header: "시도", accessorKey: "sido", size: 80 }, + { header: "구군", accessorKey: "gugun", size: 80 }, + { header: "행정구역", accessorKey: "admdstNm", size: 100 }, + { header: "이전년도", accessorKey: "prvyrVl", size: 80 }, + { header: "당해년도", accessorKey: "cryrVl", size: 80 }, + { + header: "R", + accessorKey: "chgRt", + size: 60, + cell: (info) => Number(info.getValue() ?? 0).toFixed(2), + }, + { + header: "S", + accessorKey: "trsfRt", + size: 60, + cell: (info) => Number(info.getValue() ?? 0).toFixed(2), + }, + { + header: "E", + accessorKey: "otlrTrsfRt", + size: 60, + cell: (info) => Number(info.getValue() ?? 0).toFixed(2), + }, + { + header: "상태", + accessorKey: "otlrYn", + size: 60, + cell: (info) => { + const val = info.getValue(); + if (val === "y") + return ( + + 정상 + + ); + if (val === "n") + return ( + + 이상치 + + ); + return "-"; + }, + }, + ], + [], + ); + + const table = useReactTable({ + data: rowData, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + initialState: { + pagination: { pageSize: 10 }, + }, + }); + + const handleSidoChange = (val: string) => { + onSidoSelect(val === "ALL" ? null : val); + setSelectedGugun("ALL"); + }; + + return ( +
+
+
+ + + +
+ +
+ + +
+
+ +
+ + {isLoading && ( +
+ 데이터를 불러오는 중... +
+ )} + {!isLoading && (!vrfcTrgtSn || rowData.length === 0) && ( +
+ {vrfcTrgtSn + ? "표시할 항목이 없습니다." + : "항목을 선택해주세요"} +
+ )} +
+
+ ); +}; + +export default RegionalGrid; diff --git a/apps/st/src/components/statistics/verification/detail/ScatterChart.tsx b/apps/st/src/components/statistics/verification/detail/ScatterChart.tsx new file mode 100644 index 000000000..97eef64c3 --- /dev/null +++ b/apps/st/src/components/statistics/verification/detail/ScatterChart.tsx @@ -0,0 +1,253 @@ +import React, { useMemo } from "react"; +import { Scatter } from "react-chartjs-2"; + +import { useQuery } from "@tanstack/react-query"; +import { + Chart as ChartJS, + Legend, + LinearScale, + LineElement, + PointElement, + ScatterController, + Tooltip, +} from "chart.js"; +import zoomPlugin from "chartjs-plugin-zoom"; + +import { ENDPOINT } from "@/shared/config"; +import { CodeApi } from "@/entities/code/api/code-service"; +import { stAnlsRsltApi } from "@/entities/statistics/api/statistics-analysis-result-api"; +import type { StAnlsRsltResponse } from "@/entities/statistics/model/statistics-analysis-result-interface"; + +ChartJS.register( + LinearScale, + PointElement, + LineElement, + Tooltip, + Legend, + ScatterController, + zoomPlugin, +); + +interface ScatterChartProps { + anlsSn: number; + vrfcTrgtSn: string | null; + selectedSido: string | null; +} + +const ScatterChart: React.FC = ({ + anlsSn, + vrfcTrgtSn, + selectedSido, +}) => { + const { data: regionCodes } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CODE, "LAESECD"], + queryFn: () => CodeApi.getCode("LAESECD"), + }); + + const selectedSidoName = useMemo(() => { + if (!selectedSido || !regionCodes) return null; + return ( + regionCodes.find((c) => c.lwpoCd === selectedSido)?.lwpoCdNm || null + ); + }, [regionCodes, selectedSido]); + + const { data: resultData, isLoading: isLoadingGrid } = useQuery({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT, + "list", + anlsSn, + vrfcTrgtSn, + ], + queryFn: () => + stAnlsRsltApi.getResultList({ + anlsSn, + vrfcTrgtSn: Number(vrfcTrgtSn), + page: 0, + size: 1000, + }), + enabled: !!anlsSn && !!vrfcTrgtSn, + }); + + const { data: chartData, isLoading: isLoadingChart } = useQuery({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT, + "chart", + anlsSn, + vrfcTrgtSn, + ], + queryFn: () => stAnlsRsltApi.getChartData(anlsSn, Number(vrfcTrgtSn)), + enabled: !!anlsSn && !!vrfcTrgtSn, + }); + + const isLoading = isLoadingGrid || isLoadingChart; + + const chartConfig = useMemo(() => { + if (!resultData || !chartData) return null; + + const rawPoints = (resultData.content ?? []) as StAnlsRsltResponse[]; + const boundaries = chartData.list ?? []; + + const sortedBoundaries = [...boundaries].sort( + (a, b) => a.xPrev - b.xPrev, + ); + const upperLineData = sortedBoundaries.map((b) => ({ + x: b.xPrev, + y: b.yUpper, + })); + const lowerLineData = sortedBoundaries.map((b) => ({ + x: b.xPrev, + y: b.yLower, + })); + + const normalPoints: { + x: number; + y: number; + item: StAnlsRsltResponse; + }[] = []; + const outlierPoints: { + x: number; + y: number; + item: StAnlsRsltResponse; + }[] = []; + + rawPoints.forEach((item) => { + if (selectedSidoName && item.sido !== selectedSidoName) { + return; + } + + const pt = { + x: Number(item.prvyrVl ?? 0), + y: Number(item.cryrVl ?? 0), + item, + }; + + if (item.otlrYn.toLowerCase() === "y") { + normalPoints.push(pt); + } else { + outlierPoints.push(pt); + } + }); + + const data = { + datasets: [ + { + label: "상한선", + data: upperLineData, + type: "line" as const, + borderColor: "rgba(255, 99, 132, 0.8)", + borderWidth: 2, + fill: false, + pointRadius: 0, + tension: 0.1, + }, + { + label: "하한선", + data: lowerLineData, + type: "line" as const, + borderColor: "rgba(54, 162, 235, 0.8)", + borderWidth: 2, + fill: false, + pointRadius: 0, + tension: 0.1, + }, + { + label: "이상치", + data: outlierPoints, + backgroundColor: "rgba(255, 99, 132, 0.7)", + pointRadius: 5, + pointHoverRadius: 8, + }, + { + label: "정상", + data: normalPoints, + backgroundColor: "rgba(75, 192, 192, 0.7)", + pointRadius: 4, + pointHoverRadius: 7, + }, + ], + }; + + const options = { + responsive: true, + maintainAspectRatio: false, + plugins: { + zoom: { + pan: { + enabled: true, + mode: "xy" as const, + }, + zoom: { + wheel: { + enabled: true, + }, + pinch: { + enabled: true, + }, + mode: "xy" as const, + }, + }, + tooltip: { + callbacks: { + label: (context: any) => { + if (context.dataset.type === "line") { + return `${context.dataset.label}: (${context.parsed.x.toFixed(2)}, ${context.parsed.y.toFixed(2)})`; + } + const pt = context.raw as { + x: number; + y: number; + item: StAnlsRsltResponse; + }; + return `${pt.item.sido} ${pt.item.gugun}: 이전(${pt.x}), 당해(${pt.y})`; + }, + }, + }, + }, + scales: { + x: { + type: "linear" as const, + position: "bottom" as const, + title: { + display: true, + text: "이전년도 값", + }, + }, + y: { + type: "linear" as const, + title: { + display: true, + text: "당해년도 값", + }, + }, + }, + }; + + return { data, options }; + }, [resultData, chartData, selectedSidoName]); + + return ( +
+ {!vrfcTrgtSn ? ( +
+ 항목을 선택해주세요 +
+ ) : isLoading ? ( +
+ 차트를 불러오는 중... +
+ ) : chartConfig ? ( +
+ +
+ ) : ( +
+ 표시할 데이터가 없습니다. +
+ )} +
+ ); +}; + +export default ScatterChart; diff --git a/apps/st/src/components/statistics/verification/detail/VerificationList.tsx b/apps/st/src/components/statistics/verification/detail/VerificationList.tsx new file mode 100644 index 000000000..62463b036 --- /dev/null +++ b/apps/st/src/components/statistics/verification/detail/VerificationList.tsx @@ -0,0 +1,141 @@ +import React, { useEffect, useMemo } from "react"; + +import { useQuery } from "@tanstack/react-query"; +import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; + +import { ENDPOINT } from "@/shared/config"; +import { stAnlsDtlApi } from "@/entities/statistics/api/statistics-analysis-detail-api"; +import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service"; +import type { StAnlsDtlResponse } from "@/entities/statistics/model/statistics-analysis-detail-interface"; +import { DataTable } from "@common/components"; + +interface VerificationListProps { + anlsSn: number; + selectedVrfcTrgtSn: string | null; + onSelect: (vrfcTrgtSn: string) => void; +} + +const VerificationList: React.FC = ({ + anlsSn, + selectedVrfcTrgtSn, + onSelect, +}) => { + const { data: verificationTargetCodes } = useQuery({ + queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG, "codes"], + queryFn: () => + verificationTargetManagementApi.getVerificationTargetCodes(), + }); + + const targetCodeMap = useMemo(() => { + const map = new Map(); + if (verificationTargetCodes) { + verificationTargetCodes.forEach((item) => { + map.set(item.vrfcTrgtSn, item.vrfcTrgtNm); + }); + } + return map; + }, [verificationTargetCodes]); + + const { data: detailData, isLoading } = useQuery({ + queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL, "list", anlsSn], + queryFn: () => + stAnlsDtlApi.getDetailList({ anlsSn, page: 0, size: 100 }), + enabled: !!anlsSn, + }); + + const rowData = useMemo(() => { + return (detailData?.content ?? []) as StAnlsDtlResponse[]; + }, [detailData]); + + const columns = useMemo( + () => [ + { + header: "검증 항목", + accessorFn: (row: StAnlsDtlResponse) => + targetCodeMap.get(String(row.vrfcTrgtSn)) || + String(row.vrfcTrgtSn), + id: "vrfcTrgtNm", + size: 150, + }, + { + header: "μ", + accessorKey: "ipcrCntVl", + size: 60, + }, + { + header: "Percentile", + accessorKey: "pctlCrtrVl", + size: 60, + }, + { + header: "A", + accessorKey: "scopAjmtCntVl", + size: 60, + }, + { + header: "C", + accessorKey: "thrsCntVl", + size: 60, + }, + { + header: "정상", + accessorKey: "nmlDataNocs", + size: 70, + }, + { + header: "제외", + accessorKey: "exclDataNocs", + size: 70, + }, + { + header: "이상", + accessorKey: "otlrDataNocs", + size: 70, + }, + ], + [targetCodeMap], + ); + + const table = useReactTable({ + data: rowData, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + const handleRowClick = (row: StAnlsDtlResponse) => { + onSelect(String(row.vrfcTrgtSn)); + }; + + useEffect(() => { + if (!selectedVrfcTrgtSn && rowData.length > 0) { + const firstTargetSn = rowData[0]?.vrfcTrgtSn; + if (firstTargetSn) onSelect(String(firstTargetSn)); + } + }, [selectedVrfcTrgtSn, rowData, onSelect]); + + return ( +
+ {isLoading && ( +
+ 데이터를 불러오는 중... +
+ )} + {!isLoading && rowData.length === 0 && ( +
+ 표시할 항목이 없습니다. +
+ )} + {!isLoading && rowData.length > 0 && ( + + )} +
+ ); +}; + +export default VerificationList; diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index 59948afb8..d9bf1997c 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -1,5 +1,6 @@ import React from "react"; import { ValueType } from "realgrid"; +import { type SewerageSupplyRateStatusInfo } from "@/entities/statistics/model/statistics-sewage-supply-rate-input-interface"; // RealGrid 타입 정의 /** setColumns에 전달하는 컬럼 정보 (footer 배열/단일 객체 포함) */ @@ -10,8 +11,10 @@ export type RealGridColumnInfo = { editable?: boolean; valueType?: import("realgrid").ValueType; dataFormat?: string; + width?: number; styleName?: string; - mergeRule?: { criteria: "value" }; + mergeRule?: { criteria: "value" | string }; + sortable?: boolean; footer?: | { text?: string; @@ -130,9 +133,13 @@ export type ColumnConfig = { editable?: boolean; // 셀 편집 가능 여부 /** 그리드에서 컬럼 숨김 (수식용 필드 등) */ visible?: boolean; + /** 컬럼 너비 (px). 지정 시 ... 생략 없이 텍스트 표시에 유리 */ + width?: number; /** 셀에 적용할 CSS 클래스명 (예: 'rg-cell-readonly', 'rg-cell-total') */ styleName?: string; mergeCells?: boolean; + /** 병합 시 그룹 기준 필드(예: 시군구). 지정 시 같은 그룹 내에서만 value 기준 병합 */ + mergeGroupField?: string; sortdisable?: boolean; sortPreserveGroupOrder?: SortPreserveGroupOrder; valueType?: ValueType; @@ -211,6 +218,10 @@ export type CellStyleCallbackParams = { export type CellStyleCallback = (params: CellStyleCallbackParams) => string | undefined; +/** 셀 hover 시 툴팁 문구 (반환 시 해당 문구 표시, 검증 오류 메시지 등) */ +export type CellTooltipCallbackParams = { row: Record; fieldName: string }; +export type CellTooltipCallback = (params: CellTooltipCallbackParams) => string | undefined; + export type DataGridProps = { columnInfo: ColumnInfo; rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주) @@ -225,11 +236,14 @@ export type DataGridProps = { >; /** 셀 단위로 styleName을 동적으로 결정 (예: 검증 오류 셀) */ cellStyleCallback?: CellStyleCallback; + /** 셀 hover 시 툴팁 문구 (예: verificationResult의 rsnExpln) */ + cellTooltipCallback?: CellTooltipCallback; /** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */ getGridDataRef?: { current: (() => Record[]) | null; }; onRowClick?: (rowData: Record) => void; // 행 클릭 이벤트 + statusInfo?: SewerageSupplyRateStatusInfo | null; }; export type TabDataGridProps = { diff --git a/apps/st/src/components/util/column/input/DoList.ts b/apps/st/src/components/util/column/input/DoList.ts new file mode 100644 index 000000000..2b58bd57b --- /dev/null +++ b/apps/st/src/components/util/column/input/DoList.ts @@ -0,0 +1,23 @@ +export const doList = [ + "4100000000", + "4200000000", + "4300000000", + "4400000000", + "4500000000", + "4600000000", + "4700000000", + "4800000000", +]; + +export const needBsnmFrgnrPoplSmSidoList = [ + "3600000000", + "4100000000", + "4200000000", + "4300000000", + "4400000000", + "4500000000", + "4600000000", + "4700000000", + "4800000000", + "5000000000", +]; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts b/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts index 41a9c5700..566f7ae48 100644 --- a/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts +++ b/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts @@ -7,13 +7,13 @@ import { ValueType } from "realgrid"; export const SeweragePenetrationRateInpColumn = { columns: [ // 행정구역 - { name: 'sido', fieldName: 'sido', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' }, - { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' }, + { name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' }, + { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' }, // 수계 및 권역 { name: 'rvbsn', fieldName: 'rvbsn', header: { text: '수계' }, editable: false, styleName: 'rg-cell-readonly' }, - { name: 'bstrm', fieldName: 'bstrm', header: { text: '지류' } }, + { name: 'trbtrNm', fieldName: 'trbtrNm', header: { text: '지류' } }, // 수계영향권별분류 { name: 'mdbsn', fieldName: 'mdbsn', header: { text: '세부단위유역' }, editable: false, styleName: 'rg-cell-readonly' }, @@ -21,121 +21,121 @@ export const SeweragePenetrationRateInpColumn = { { name: 'mdmbsn', fieldName: 'mdmbsn', header: { text: '소권역' }, editable: false, styleName: 'rg-cell-readonly' }, // 특별대책지역 - { name: 'speclCntrplnAr', fieldName: 'speclCntrplnAr', header: { text: '특별대책지역' } }, + { name: 'spcCtpnRgnCn', fieldName: 'spcCtpnRgnCn', header: { text: '특별대책지역' } }, // 행정참고자료인구 - { name: 'totalPopl', fieldName: 'totalPopl', header: { text: '총인구' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' }, - { name: 'poplSm', fieldName: 'poplSm', header: { text: '내국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' }, - { name: 'frgnrPoplSm', fieldName: 'frgnrPoplSm', header: { text: '외국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' }, + { name: 'pbadmsWholPpltnCnt', fieldName: 'pbadmsWholPpltnCnt', header: { text: '총인구' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' }, + { name: 'domplWholPpltnCnt', fieldName: 'domplWholPpltnCnt', header: { text: '내국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' }, + { name: 'frgnrWholPpltnCnt', fieldName: 'frgnrWholPpltnCnt', header: { text: '외국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' }, // 행정참고자료면적 - { name: 'area', fieldName: 'area', header: { text: '행정참고자료면적(㎢)' }, footer: footerDecimal3, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000', styleName: 'rg-cell-readonly' }, + { name: 'refArea', fieldName: 'refArea', header: { text: '행정참고자료면적(㎢)' }, footer: footerDecimal3, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000', styleName: 'rg-cell-readonly' }, // 총인구 { - name: 'popltnSm', - fieldName: 'popltnSm', + name: 'wholPpltnCnt', + fieldName: 'wholPpltnCnt', 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 }, + computed: { fields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt', 'unprcsPpltnCnt', 'drtwPrcsPpltnCnt', 'spctnkPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, // 총면적 { - name: 'arSm', - fieldName: 'arSm', + name: 'wholArea', + fieldName: 'wholArea', header: { text: '총면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000', - computed: { fields: ['swgeProcZoneInnerAr', 'swgeProcZoneOuterAr'], type: 'sum' as const }, + computed: { fields: ['staaArea', 'staaExcpArea'], type: 'sum' as const }, styleName: 'rg-cell-total', }, // 하수처리구역 내 > 합계 { - name: 'swgeProcZoneInnerSm', - fieldName: 'swgeProcZoneInnerSm', + name: 'wholSwtrmtPpltnCnt', + fieldName: 'wholSwtrmtPpltnCnt', header: { text: '합계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], type: 'sum' as const }, + computed: { fields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, // 하수처리구역 내 > 공공하수처리인구 { - name: 'pblswrgPopltnSm', - fieldName: 'pblswrgPopltnSm', + name: 'wholPblSwtrmtPpltnCnt', + fieldName: 'wholPblSwtrmtPpltnCnt', header: { text: '소계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt'], type: 'sum' as const }, + computed: { fields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, - { name: 'pblswrgPrmTrt', fieldName: 'pblswrgPrmTrt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'pblswrgScdTrt', fieldName: 'pblswrgScdTrt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'pblswrgTertTrt', fieldName: 'pblswrgTertTrt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts1SwtrmtPpltnCnt', fieldName: 'ts1SwtrmtPpltnCnt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts2SwtrmtPpltnCnt', fieldName: 'ts2SwtrmtPpltnCnt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts3SwtrmtPpltnCnt', fieldName: 'ts3SwtrmtPpltnCnt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 하수처리구역 내 > 폐수처리인구 { - name: 'wstrPopltnSm', - fieldName: 'wstrPopltnSm', + name: 'wholWwtPrcsPpltnCnt', + fieldName: 'wholWwtPrcsPpltnCnt', header: { text: '소계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], type: 'sum' as const }, + computed: { fields: ['ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, - { name: 'wstrPrmTrt', fieldName: 'wstrPrmTrt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'wstrScdTrt', fieldName: 'wstrScdTrt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'wstrTertTrt', fieldName: 'wstrTertTrt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts1WwtPrcsPpltnCnt', fieldName: 'ts1WwtPrcsPpltnCnt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts2WwtPrcsPpltnCnt', fieldName: 'ts2WwtPrcsPpltnCnt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts3WwtPrcsPpltnCnt', fieldName: 'ts3WwtPrcsPpltnCnt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 하수처리구역 내 > 미접속인구 - { name: 'notConectPopltn', fieldName: 'notConectPopltn', header: { text: '미접속인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'yetCntnPpltnCnt', fieldName: 'yetCntnPpltnCnt', header: { text: '미접속인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 하수처리구역 내 > 면적 - { name: 'swgeProcZoneInnerAr', fieldName: 'swgeProcZoneInnerAr', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'staaArea', fieldName: 'staaArea', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, // 하수처리구역 외 { - name: 'swgeProcZoneOuterSm', - fieldName: 'swgeProcZoneOuterSm', + name: 'wholStaaExcpPpltnCnt', + fieldName: 'wholStaaExcpPpltnCnt', header: { text: '합계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], type: 'sum' as const }, + computed: { fields: ['unprcsPpltnCnt', 'drtwPrcsPpltnCnt', 'spctnkPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, - { name: 'townUnprocPopltn', fieldName: 'townUnprocPopltn', header: { text: '미처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'townSwttPopltn', fieldName: 'townSwttPopltn', header: { text: '오수처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'townWrrtnPopltn', fieldName: 'townWrrtnPopltn', header: { text: '정화조인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'swgeProcZoneOuterAr', fieldName: 'swgeProcZoneOuterAr', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'unprcsPpltnCnt', fieldName: 'unprcsPpltnCnt', header: { text: '미처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'drtwPrcsPpltnCnt', fieldName: 'drtwPrcsPpltnCnt', header: { text: '오수처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'spctnkPpltnCnt', fieldName: 'spctnkPpltnCnt', header: { text: '정화조인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'staaExcpArea', fieldName: 'staaExcpArea', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, // 공공하수처리구역 인구보급률 (하수처리구역 내 인구 / 전체 인구 * 100) { - name: 'pblswrgPopltnSuprt', - fieldName: 'pblswrgPopltnSuprt', + name: 'staaPsplyRt', + fieldName: 'staaPsplyRt', header: { text: '공공하수처리구역인구보급률(%)' }, footer: footerAvgDecimal, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', computed: { - numeratorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], - denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], + numeratorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt'], + denominatorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt', 'unprcsPpltnCnt', 'drtwPrcsPpltnCnt', 'spctnkPpltnCnt'], type: 'pblswrgPopltnSuprtRate' as const }, styleName: 'rg-cell-total', @@ -143,16 +143,16 @@ export const SeweragePenetrationRateInpColumn = { // 고도처리 인구보급률 (고도처리 인구 / 전체 인구 * 100) { - name: 'adtrtPopltnSuprt', - fieldName: 'adtrtPopltnSuprt', + name: 'altdPrcsPsplyRt', + fieldName: 'altdPrcsPsplyRt', header: { text: '고도처리인구보급률(%)' }, footer: footerAvgDecimal, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', computed: { - numeratorFields: ['pblswrgTertTrt', 'wstrTertTrt'], - denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], + numeratorFields: ['ts3SwtrmtPpltnCnt', 'ts3WwtPrcsPpltnCnt'], + denominatorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt', 'unprcsPpltnCnt', 'drtwPrcsPpltnCnt', 'spctnkPpltnCnt'], type: 'adtrtPopltnSuprtRate' as const }, styleName: 'rg-cell-total', @@ -160,27 +160,27 @@ export const SeweragePenetrationRateInpColumn = { // 하수도 설치율 { - name: 'swrgInstlrt', - fieldName: 'swrgInstlrt', + name: 'sewerInstlRt', + fieldName: 'sewerInstlRt', header: { text: '하수도설치율(%)' }, footer: footerAvgDecimal, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', computed: { - numeratorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'swgeProcZoneOuterSm'], - denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], - thirdFields: ['piplSupprt'], + numeratorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt', 'wholStaaExcpPpltnCnt'], + denominatorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt'], + thirdFields: ['swagePplPsplyRt'], type: 'swrgInstlrtRate' as const }, styleName: 'rg-cell-total', }, // 비고 - { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - { name: 'piplSupprt', fieldName: 'piplSupprt', header: { text: '하수관로보급률(%)' }, visible: false }, - { name: 'adstCd', fieldName: 'adstCd', header: { text: '행정코드' }, visible: false }, + { name: 'swagePplPsplyRt', fieldName: 'swagePplPsplyRt', header: { text: '하수관로보급률(%)' }, visible: false }, + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정코드' }, visible: false }, ], columnLayout: [ @@ -189,49 +189,49 @@ export const SeweragePenetrationRateInpColumn = { hideChildHeaders: false, header: { text: '행정구역' }, items: [ - { column: 'sido', footerUserSpans: [{ colspan: 3 }] }, - 'gugun', - 'adstDong', + { column: 'ctpvNm', footerUserSpans: [{ colspan: 3 }] }, + 'sggNm', + 'admdstNm', ], }, 'rvbsn', - 'bstrm', + 'trbtrNm', { name: 'groupB', hideChildHeaders: false, header: { text: '수계영향권별분류' }, items: ['mdbsn', 'lgbsn', 'mdmbsn'], }, - 'speclCntrplnAr', + 'spcCtpnRgnCn', { name: 'groupC', hideChildHeaders: false, header: { text: '행정참고자료인구' }, - items: ['totalPopl', 'poplSm', 'frgnrPoplSm'], + items: ['pbadmsWholPpltnCnt', 'domplWholPpltnCnt', 'frgnrWholPpltnCnt'], }, - 'area', - 'popltnSm', - 'arSm', + 'refArea', + 'wholPpltnCnt', + 'wholArea', { name: 'groupD', hideChildHeaders: false, header: { text: '하수처리구역내' }, items: [ - 'swgeProcZoneInnerSm', + 'wholSwtrmtPpltnCnt', { name: 'pblswrgGroup', hideChildHeaders: false, header: { text: '공공하수처리인구(명)' }, - items: ['pblswrgPopltnSm', 'pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt'], + items: ['wholPblSwtrmtPpltnCnt', 'ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt'], }, { name: 'wstrGroup', hideChildHeaders: false, header: { text: '폐수처리인구(명)' }, - items: ['wstrPopltnSm', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], + items: ['wholWwtPrcsPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt'], }, - 'notConectPopltn', - 'swgeProcZoneInnerAr' + 'yetCntnPpltnCnt', + 'staaArea' ], }, { @@ -239,18 +239,18 @@ export const SeweragePenetrationRateInpColumn = { hideChildHeaders: false, header: { text: '하수처리구역외' }, items: [ - 'swgeProcZoneOuterSm', - 'townUnprocPopltn', - 'townSwttPopltn', - 'townWrrtnPopltn', - 'swgeProcZoneOuterAr' + 'wholStaaExcpPpltnCnt', + 'unprcsPpltnCnt', + 'drtwPrcsPpltnCnt', + 'spctnkPpltnCnt', + 'staaExcpArea' ], }, - 'pblswrgPopltnSuprt', - 'adtrtPopltnSuprt', - 'swrgInstlrt', - 'rmk', - 'piplSupprt', - 'adstCd', + 'staaPsplyRt', + 'altdPrcsPsplyRt', + 'sewerInstlRt', + 'rmrk', + 'swagePplPsplyRt', + 'admdstCd', ] }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/StatsInputStatusInpColumn.ts b/apps/st/src/components/util/column/input/StatsInputStatusInpColumn.ts index 09d10d3d1..7ed87ddc3 100644 --- a/apps/st/src/components/util/column/input/StatsInputStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/StatsInputStatusInpColumn.ts @@ -1,4 +1,4 @@ -import { ValueType } from "realgrid"; +// import { ValueType } from "realgrid"; /** * 하수도 통계 관리 > 00. 통계 입력 현황 @@ -6,40 +6,42 @@ import { ValueType } from "realgrid"; */ export const StatsInputStatusInpColumn = { columns: [ - { name: 'sido', fieldName: '시도', header: { text: '시도' } }, - { name: 'sgg', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'menu_nm', fieldName: '구분', header: { text: '구분' } }, - { name: 'locg_status_nm', fieldName: '진행상황', header: { text: '진행상황' } }, - { name: 'locg_status', fieldName: '작성률', header: { text: '작성률' }, valueType: ValueType.NUMBER }, - { name: 'locg_charger', fieldName: '작성자', header: { text: '작성자' } }, - { name: 'locg_cttpc', fieldName: '작성자연락처', header: { text: '작성자연락처' } }, - { name: 'locg_dept', fieldName: '부서', header: { text: '부서' } }, - { name: 'locg_job', fieldName: '직제', header: { text: '직제' } }, - { name: 'locg_cnfirm_nm', fieldName: '승인자', header: { text: '승인자' } }, - { name: 'locg_cnfirm_dept', fieldName: '부서', header: { text: '부서' } }, - { name: 'locg_cnfirm_job', fieldName: '직제', header: { text: '직제' } }, - { name: 'return_opinion_sm', fieldName: '반려사유', header: { text: '반려사유' } }, + { name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, editable: false }, + { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false }, + { name: 'statsCd', fieldName: 'statsCd', header: { text: '메뉴코드' }, visible: false, editable: false }, + { name: 'menuTabName', fieldName: 'menuTabName', header: { text: '구분' }, editable: false, width: 220 }, + { name: 'locgStatusNm', fieldName: 'locgStatusNm', header: { text: '진행상황' }, editable: false }, + // { name: 'inputRate', fieldName: 'inputRate', header: { text: '작성률' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'laePicNm', fieldName: 'laePicNm', header: { text: '작성자' }, editable: false }, + { name: 'laePicTelNo', fieldName: 'laePicTelNo', header: { text: '작성자연락처' }, editable: false }, + { name: 'laeKbrdrDeptNm', fieldName: 'laeKbrdrDeptNm', header: { text: '부서' }, editable: false }, + { name: 'laeKbrdrJbgdNm', fieldName: 'laeKbrdrJbgdNm', header: { text: '직제' }, editable: false }, + { name: 'laeIdfrNm', fieldName: 'laeIdfrNm', header: { text: '승인자' }, editable: false }, + { name: 'laeIdfrDeptNm', fieldName: 'laeIdfrDeptNm', header: { text: '부서' }, editable: false }, + { name: 'laeIdfrJbgdNm', fieldName: 'laeIdfrJbgdNm', header: { text: '직제' }, editable: false }, + { name: 'rjctOpnnCn', fieldName: 'rjctOpnnCn', header: { text: '반려사유' }, editable: false }, ], columnLayout: [ - 'sido', - 'sgg', - 'menu_nm', + 'ctpvNm', + 'sggNm', + 'statsCd', + 'menuTabName', { name: 'groupA', hideChildHeaders: false, header: { text: '하수도사업자' }, items: [ - 'locg_status_nm', - 'locg_status', - 'locg_charger', - 'locg_cttpc', - 'locg_dept', - 'locg_job', - 'locg_cnfirm_nm', - 'locg_cnfirm_dept', - 'locg_cnfirm_job', - 'return_opinion_sm' + 'locgStatusNm', + // 'inputRate', + 'laePicNm', + 'laePicTelNo', + 'laeKbrdrDeptNm', + 'laeKbrdrJbgdNm', + 'laeIdfrNm', + 'laeIdfrDeptNm', + 'laeIdfrJbgdNm', + 'rjctOpnnCn' ], }, ] diff --git a/apps/st/src/components/util/column/management/LogHistoryColumn.ts b/apps/st/src/components/util/column/management/LogHistoryColumn.ts new file mode 100644 index 000000000..74efa78b4 --- /dev/null +++ b/apps/st/src/components/util/column/management/LogHistoryColumn.ts @@ -0,0 +1,21 @@ +import { ValueType } from "realgrid"; + +export const LogHistoryColumn = { + columns: [ + { name: 'statsLogSn', fieldName: 'statsLogSn', header: { text: '일련번호' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'menuNm', fieldName: 'menuNm', header: { text: '메뉴명' }, editable: false }, + { name: 'tblNm', fieldName: 'tblNm', header: { text: '테이블명' }, visible: false, editable: false }, + { name: 'dmndFwkNm', fieldName: 'dmndFwkNm', header: { text: '요청 기능명' }, editable: false }, + { name: 'rgtrId', fieldName: 'rgtrId', header: { text: '등록자' }, editable: false }, + { name: 'regDt', fieldName: 'regDt', header: { text: '등록일' }, editable: false }, + ], + + columnLayout: [ + 'statsLogSn', + 'menuNm', + 'tblNm', + 'dmndFwkNm', + 'rgtrId', + 'regDt' + ] +}; \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts index 058eb39e3..b42f1637a 100644 --- a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts +++ b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts @@ -1,106 +1,196 @@ import { ValueType } from "realgrid"; +import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; +import type { GridView } from "@/components/type/DataGrid.type"; -// 하수도 통계 관리 05.펌프장 export const rawPumpingStationInfoList = { columns: [ /* 1. 행정구역 및 기본정보 */ - { name: 'sido', fieldName: '시도', header: { text: '시도' } }, - { name: 'sgg', fieldName: '구군', header: { text: '구군' } }, - { name: 'PUMP_NM', fieldName: '펌프장명', header: { text: '펌프장명' } }, - { name: 'GBN', fieldName: '구분', header: { text: '구분' } }, - { name: 'TRT_FCT_NM', fieldName: '처리장명', header: { text: '처리장명' } }, - { name: 'TYPE_GBN', fieldName: '구분(합류식분류식)', header: { text: '구분(합류식분류식)' } }, - { name: 'ADDR', fieldName: '위치', header: { text: '위치' } }, - { name: 'RN_DT', fieldName: '가동년월일 yyyy-mm-dd', header: { text: '가동년월일\nyyyy-mm-dd' } }, - { name: 'DSN_AR', fieldName: '계획배수면적(km2)', header: { text: '계획배수면적\n(km2)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + } + ] + }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'pumpNm', fieldName: 'pumpNm', header: { text: '펌프장명' }, mergeCells: true, sortdisable: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + } + ] + }, + { name: 'bscFctNm', fieldName: 'bscFctNm', header: { text: '처리장명' } }, + { name: 'pumpCls', fieldName: 'pumpCls', header: { text: '구분\n(합류/분류)' } }, + { name: 'addr', fieldName: 'addr', header: { text: '위치' } }, + { name: 'rnDt', fieldName: 'rnDt', header: { text: '가동년월일\nyyyy-mm-dd' } }, + { + name: 'planDrhgAr', fieldName: 'planDrhgAr', header: { text: '계획배수면적\n(km2)' }, + footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' + }, /* 2. 계획배수량 (m3/hr) */ - { name: 'DSN_QTY_A', fieldName: '청천시(A)', header: { text: '청천시(A)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DSN_QTY_B', fieldName: '우천시(B)', header: { text: '우천시(B)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DSN_QTY_DIFF', fieldName: '우수량(B-A)', header: { text: '우수량(B-A)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'finewtDrngVl', fieldName: 'finewtDrngVl', header: { text: '청천시(A)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtDrngVl', fieldName: 'rainwtDrngVl', header: { text: '우천시(B)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'vlWtr', fieldName: 'vlWtr', header: { text: '우수량(B-A)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, /* 3. 총 양수량 (m3/min) */ - { name: 'TOT_PUMP_QTY_O', fieldName: '오수펌프 총양수량', header: { text: '오수펌프\n총양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'TOT_PUMP_QTY_W', fieldName: '우수펌프 총양수량', header: { text: '우수펌프\n총양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrTotCpcty', fieldName: 'pltwtrTotCpcty', header: { text: '오수펌프\n총양수량' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrTotCpcty', fieldName: 'rainwtrTotCpcty', header: { text: '우수펌프\n총양수량' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, /* 4. 오수펌프 상세 (1, 2, 3) */ - { name: 'PUMP_O1_D', fieldName: '구경(mm)_오수1', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O1_Q', fieldName: '양수량(m3/min)_오수1', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O1_C', fieldName: '펌프대수_오수1', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_O1_S', fieldName: '예비대수_오수1', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpCalbr1', fieldName: 'pltwtrPumpCalbr1', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCpcty1', fieldName: 'pltwtrPumpCpcty1', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCnt1', fieldName: 'pltwtrPumpCnt1', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpPreparCnt1', fieldName: 'pltwtrPumpPreparCnt1', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + + { name: 'pltwtrPumpCalbr2', fieldName: 'pltwtrPumpCalbr2', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCpcty2', fieldName: 'pltwtrPumpCpcty2', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCnt2', fieldName: 'pltwtrPumpCnt2', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpPreparCnt2', fieldName: 'pltwtrPumpPreparCnt2', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_O2_D', fieldName: '구경(mm)_오수2', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O2_Q', fieldName: '양수량(m3/min)_오수2', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O2_C', fieldName: '펌프대수_오수2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_O2_S', fieldName: '예비대수_오수2', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - - { name: 'PUMP_O3_D', fieldName: '구경(mm)_오수3', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O3_Q', fieldName: '양수량(m3/min)_오수3', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O3_C', fieldName: '펌프대수_오수3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_O3_S', fieldName: '예비대수_오수3', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpCalbr3', fieldName: 'pltwtrPumpCalbr3', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCpcty3', fieldName: 'pltwtrPumpCpcty3', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCnt3', fieldName: 'pltwtrPumpCnt3', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpPreparCnt3', fieldName: 'pltwtrPumpPreparCnt3', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, /* 5. 우수펌프 상세 (1, 2, 3) */ - { name: 'PUMP_W1_D', fieldName: '구경(mm)_우수1', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W1_Q', fieldName: '양수량(m3/min)_우수1', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W1_C', fieldName: '펌프대수_우수1', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W1_S', fieldName: '예비대수_우수1', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpCalbr1', fieldName: 'rainwtrPumpCalbr1', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCpcty1', fieldName: 'rainwtrPumpCpcty1', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCnt1', fieldName: 'rainwtrPumpCnt1', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpPreparCnt1', fieldName: 'rainwtrPumpPreparCnt1', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W2_D', fieldName: '구경(mm)_우수2', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W2_Q', fieldName: '양수량(m3/min)_우수2', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W2_C', fieldName: '펌프대수_우수2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W2_S', fieldName: '예비대수_우수2', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpCalbr2', fieldName: 'rainwtrPumpCalbr2', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCpcty2', fieldName: 'rainwtrPumpCpcty2', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCnt2', fieldName: 'rainwtrPumpCnt2', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpPreparCnt2', fieldName: 'rainwtrPumpPreparCnt2', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W3_D', fieldName: '구경(mm)_우수3', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W3_Q', fieldName: '양수량(m3/min)_우수3', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W3_C', fieldName: '펌프대수_우수3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W3_S', fieldName: '예비대수_우수3', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpCalbr3', fieldName: 'rainwtrPumpCalbr3', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCpcty3', fieldName: 'rainwtrPumpCpcty3', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCnt3', fieldName: 'rainwtrPumpCnt3', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpPreparCnt3', fieldName: 'rainwtrPumpPreparCnt3', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - /* 6. 운영 정보 */ - { name: 'PWR_USAGE', fieldName: '연간전력사용량(kWh/년)', header: { text: '연간전력사용량\n(kWh/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + /* 6. 기타 및 운영 */ + { name: 'elcpwAmnt', fieldName: 'elcpwAmnt', header: { text: '연간전력사용량\n(kWh/년)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'groupA', header: { text: '행정구역' }, items: ['sido', 'sgg'] }, - 'PUMP_NM', 'GBN', 'TRT_FCT_NM', 'TYPE_GBN', 'ADDR', 'RN_DT', 'DSN_AR', + { + name: 'groupA', + header: { text: '행정구역' }, + items: [ + { column: 'sido', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, + 'gugun', + 'pumpNm', + ] + }, + 'displayYear', + 'bscFctNm', + 'pumpCls', + 'addr', + 'rnDt', + 'planDrhgAr', { name: 'groupB', header: { text: '계획시간대최대배수량(m3/hr)' }, - items: ['DSN_QTY_A', 'DSN_QTY_B', 'DSN_QTY_DIFF'], + items: ['finewtDrngVl', 'rainwtDrngVl', 'vlWtr'], }, { name: 'groupC', header: { text: '배수능력' }, items: [ - 'TOT_PUMP_QTY_O', + 'pltwtrTotCpcty', { name: 'pumpO_Detail', header: { text: '오수펌프 상세' }, items: [ - { name: 'O1', header: { text: '오수펌프(1)' }, items: ['PUMP_O1_D', 'PUMP_O1_Q', 'PUMP_O1_C', 'PUMP_O1_S'] }, - { name: 'O2', header: { text: '오수펌프(2)' }, items: ['PUMP_O2_D', 'PUMP_O2_Q', 'PUMP_O2_C', 'PUMP_O2_S'] }, - { name: 'O3', header: { text: '오수펌프(3)' }, items: ['PUMP_O3_D', 'PUMP_O3_Q', 'PUMP_O3_C', 'PUMP_O3_S'] } + { name: 'O1', header: { text: '오수펌프(1)' }, items: ['pltwtrPumpCalbr1', 'pltwtrPumpCpcty1', 'pltwtrPumpCnt1', 'pltwtrPumpPreparCnt1'] }, + { name: 'O2', header: { text: '오수펌프(2)' }, items: ['pltwtrPumpCalbr2', 'pltwtrPumpCpcty2', 'pltwtrPumpCnt2', 'pltwtrPumpPreparCnt2'] }, + { name: 'O3', header: { text: '오수펌프(3)' }, items: ['pltwtrPumpCalbr3', 'pltwtrPumpCpcty3', 'pltwtrPumpCnt3', 'pltwtrPumpPreparCnt3'] } ] }, - 'TOT_PUMP_QTY_W', + 'rainwtrTotCpcty', { name: 'pumpW_Detail', header: { text: '우수펌프 상세' }, items: [ - { name: 'W1', header: { text: '우수펌프(1)' }, items: ['PUMP_W1_D', 'PUMP_W1_Q', 'PUMP_W1_C', 'PUMP_W1_S'] }, - { name: 'W2', header: { text: '우수펌프(2)' }, items: ['PUMP_W2_D', 'PUMP_W2_Q', 'PUMP_W2_C', 'PUMP_W2_S'] }, - { name: 'W3', header: { text: '우수펌프(3)' }, items: ['PUMP_W3_D', 'PUMP_W3_Q', 'PUMP_W3_C', 'PUMP_W3_S'] } + { name: 'W1', header: { text: '우수펌프(1)' }, items: ['rainwtrPumpCalbr1', 'rainwtrPumpCpcty1', 'rainwtrPumpCnt1', 'rainwtrPumpPreparCnt1'] }, + { name: 'W2', header: { text: '우수펌프(2)' }, items: ['rainwtrPumpCalbr2', 'rainwtrPumpCpcty2', 'rainwtrPumpCnt2', 'rainwtrPumpPreparCnt2'] }, + { name: 'W3', header: { text: '우수펌프(3)' }, items: ['rainwtrPumpCalbr3', 'rainwtrPumpCpcty3', 'rainwtrPumpCnt3', 'rainwtrPumpPreparCnt3'] } ] } ], }, - 'PWR_USAGE', 'RMK', - ] + 'elcpwAmnt', + 'rmk', + ], + fixedColumns: 2, + rowStartNum: 3 }; export const PumpingStationInfoList = { ...rawPumpingStationInfoList, columns: rawPumpingStationInfoList.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: "rg-cell-readonly" })) }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts index 28aa25ce5..cbc536253 100644 --- a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts @@ -1,129 +1,275 @@ import { ValueType } from "realgrid"; +import { footerIntArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; +import type { GridView } from "@/components/type/DataGrid.type"; // 하수도 통계 관리 03. 하수관로 유지 관리 const rawMaintainList = [ { - tabKey: 'a', + tabKey: 'ST00030001', tabLabel: '개보수', rowStartNum: 4, fixedColumns: 2, columns: [ - /* 1. 행정 및 기본 정보 */ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, - - /* 2. 전체보수 (단위: m) */ - { name: 'RPAIR_PIPE_SM', fieldName: '계(전체보수)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_CMBN_PIPE', fieldName: '합류식(전체보수)', header: { text: '합류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_RAINWTR_PIPE', fieldName: '우수(전체보수)', header: { text: '우수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_PLTWTR_PIPE', fieldName: '오수(전체보수)', header: { text: '오수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - /* 3. 부분보수 (단위: 개소) */ - { name: 'RPAIR_PIPE_SM2', fieldName: '계(부분보수)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_CMBN_PIPE2', fieldName: '합류식(부분보수)', header: { text: '합류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_RAINWTR_PIPE2', fieldName: '우수(부분보수)', header: { text: '우수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_PLTWTR_PIPE2', fieldName: '오수(부분보수)', header: { text: '오수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - /* 4. 부속시설 (개소) */ - { name: 'MNHLE_SITE', fieldName: '합류식 맨홀', header: { text: '합류식\n맨홀' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PLTWTR_MNHLE_SITE', fieldName: '오수맨홀', header: { text: '오수맨홀' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'RAINWTR_MNHLE_SITE', fieldName: '우수맨홀', header: { text: '우수맨홀' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PLTWTR_RCVR_SITE', fieldName: '오수받이(개소)', header: { text: '오수받이(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'RAINWTR_RCVR_SITE', fieldName: '빗물받이(개소)', header: { text: '빗물받이(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SOILHOLE_SITE', fieldName: '토실(개소)', header: { text: '토실(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SOILSPLL_SITE', fieldName: '토구(개소)', header: { text: '토구(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - - /* 5. 조사량 및 비고 */ - { name: 'CCTV', fieldName: '수밀검사·CCTV조사량(m)', header: { text: '수밀검사·\nCCTV조사량\n(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { + name: 'ctpvNm', + fieldName: 'ctpvNm', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) => { + 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: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) => { + 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: 'mendngWhol', fieldName: 'mendngWhol', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'cbsysMendngPrlg', fieldName: 'cbsysMendngPrlg', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'sprsysExlnPrlg', fieldName: 'sprsysExlnPrlg', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'sprsysDrtwPrlg', fieldName: 'sprsysDrtwPrlg', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'partMendngWhol', fieldName: 'partMendngWhol', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'cbsysPartMendngPrlg', fieldName: 'cbsysPartMendngPrlg', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysExlnMendngPrlg', fieldName: 'sprsysExlnMendngPrlg', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysDrtwMendngPrlg', fieldName: 'sprsysDrtwMendngPrlg', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'cbsysMnhlCnt', fieldName: 'cbsysMnhlCnt', header: { text: '합류식\n맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysDrtwMnhlCnt', fieldName: 'sprsysDrtwMnhlCnt', header: { text: '오수맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysExlnMnhlCnt', fieldName: 'sprsysExlnMnhlCnt', header: { text: '우수맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drtwRcvprpCnt', fieldName: 'drtwRcvprpCnt', header: { text: '오수받이' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'exlnRcvprpCnt', fieldName: 'exlnRcvprpCnt', header: { text: '빗물받이' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'stofcbrCnt', fieldName: 'stofcbrCnt', header: { text: '토실' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'otflCnt', fieldName: 'otflCnt', header: { text: '토구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'cctvExmnPrlg', fieldName: 'cctvExmnPrlg', header: { text: '수밀검사·\nCCTV조사량\n(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'YR_GBN', + { + name: 'addrGroup', + header: { text: '행정구역' }, + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }, { colspan: 2 }] }, + 'sggNm', + 'admdstNm', + ] + }, + 'displayYear', { name: 'mtcPipeGroup', header: { text: '개·보수관로' }, items: [ { name: 'totalRepairGroup', header: { text: '전체보수(m)' }, - items: ['RPAIR_PIPE_SM', 'RPAIR_CMBN_PIPE', { name: 'sepTotalGroup', header: { text: '분류식' }, items: ['RPAIR_RAINWTR_PIPE', 'RPAIR_PLTWTR_PIPE'] }] + items: ['mendngWhol', 'cbsysMendngPrlg', { name: 'sepTotalGroup', header: { text: '분류식' }, items: ['sprsysExlnPrlg', 'sprsysDrtwPrlg'] }] }, { name: 'partRepairGroup', header: { text: '부분보수(개소)' }, - items: ['RPAIR_PIPE_SM2', 'RPAIR_CMBN_PIPE2', { name: 'sepPartGroup', header: { text: '분류식' }, items: ['RPAIR_RAINWTR_PIPE2', 'RPAIR_PLTWTR_PIPE2'] }] + items: ['partMendngWhol', 'cbsysPartMendngPrlg', { name: 'sepPartGroup', header: { text: '분류식' }, items: ['sprsysExlnMendngPrlg', 'sprsysDrtwMendngPrlg'] }] } ] }, - 'MNHLE_SITE', - { name: 'sepMnhleGroup', header: { text: '분류식' }, items: ['PLTWTR_MNHLE_SITE', 'RAINWTR_MNHLE_SITE'] }, - 'PLTWTR_RCVR_SITE', 'RAINWTR_RCVR_SITE', - { name: 'soilGroup', header: { text: '토실·토구(개소)' }, items: ['SOILHOLE_SITE', 'SOILSPLL_SITE'] }, - 'CCTV', 'RMK' + 'cbsysMnhlCnt', + { name: 'sepMnhleGroup', header: { text: '분류식' }, items: ['sprsysDrtwMnhlCnt', 'sprsysExlnMnhlCnt'] }, + 'drtwRcvprpCnt', 'exlnRcvprpCnt', + { name: 'soilGroup', header: { text: '토실·토구(개소)' }, items: ['stofcbrCnt', 'otflCnt'] }, + 'cctvExmnPrlg', 'rmrk' ] }, { - tabKey: 'b', + tabKey: 'ST00030002', tabLabel: '관로준설', rowStartNum: 4, fixedColumns: 2, columns: [ /* 1. 행정 및 기본 정보 */ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, - + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; + } + }, + { + valueCallback: (grid: GridView) => { + 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) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; + } + } + ] + }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const uniqueValues = new Set(); + for (let i = 0; i < grid.getDataSource().getRowCount(); i++) { + const val = grid.getDataSource().getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > 0 ? divisionValues[0] + ' 합계' : ''; + } + }, + { + valueCallback: (grid: GridView) => { + const uniqueValues = new Set(); + for (let i = 0; i < grid.getDataSource().getRowCount(); i++) { + const val = grid.getDataSource().getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > 1 ? divisionValues[1] + ' 합계' : ''; + } + } + ] + }, + /* 2. 시설 기본 정보 */ - { name: 'PIPL_FCLTY_EXTN', fieldName: '하수관로 총시설연장(m)', header: { text: '하수관로\n총시설연장\n(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'piplFcltyExtn', fieldName: 'piplFcltyExtn', header: { text: '하수관로\n총시설연장\n(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, /* 3. 준설연장 및 개소수 */ - { name: 'DRDG_PIPE_SM', fieldName: '합계(준설)', header: { text: '합계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DRDG_CMBN_PIPE', fieldName: '합류식(준설)', header: { text: '합류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DRDG_RAINWTR_PIPE', fieldName: '우수(준설)', header: { text: '우수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DRDG_PLTWTR_PIPE', fieldName: '오수(준설)', header: { text: '오수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'MNHLE_SITE_DRDG', fieldName: '맨홀(개소)', header: { text: '맨홀\n(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PLTWTR_RCVR_SITE_DRDG', fieldName: '오·우수받이(개소)', header: { text: '오·우수받이\n(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SOILSPLL_SOILHOLE_SITE', fieldName: '토실·토구(개소)', header: { text: '토실·토구\n(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drdgPipeSm', fieldName: 'drdgPipeSm', header: { text: '합계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgCmbnPipe', fieldName: 'drdgCmbnPipe', header: { text: '합류식' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgRainwtrPipe', fieldName: 'drdgRainwtrPipe', header: { text: '우수' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgPltwtrPipe', fieldName: 'drdgPltwtrPipe', header: { text: '오수' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'mnhleSite', fieldName: 'mnhleSite', header: { text: '맨홀\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrRcvrSite', fieldName: 'pltwtrRcvrSite', header: { text: '오·우수받이\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'soilspllSoilholeSite', fieldName: 'soilspllSoilholeSite', header: { text: '토실·토구\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, /* 4. 준설 계획 및 율 */ - { name: 'DRDG_TRGET_EXTN', fieldName: '준설 계획 연장(m)', header: { text: '준설 계획\n연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DRDG_TRGET_RT', fieldName: '계획대비 준설율(%)', header: { text: '계획대비\n준설율\n(%)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PIPL_FCLTY_RT', fieldName: '준설계획 연장대비 준설율(%)', header: { text: '준설계획\n연장대비\n준설율(%)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgTrgetExtn', fieldName: 'drdgTrgetExtn', header: { text: '준설 계획\n연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgTrgetRt', fieldName: 'drdgTrgetRt', header: { text: '계획대비\n준설율\n(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'piplFcltyRt', fieldName: 'piplFcltyRt', header: { text: '준설계획\n연장대비\n준설율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, /* 5. 준설실적 */ - { name: 'DRDG_ACMSLT_AMOUNT', fieldName: '금액(백만원)', header: { text: '금액\n(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'DRDG_ACMSLT_QY', fieldName: '준설량(㎥)', header: { text: '준설량\n(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drdgAcmsltAmount', fieldName: 'drdgAcmsltAmount', header: { text: '금액\n(백만원)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drdgAcmsltQy', fieldName: 'drdgAcmsltQy', header: { text: '준설량\n(㎥)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, /* 6. 준설토사처리 */ - { name: 'DRDG_ACMSLT_UNTPC', fieldName: '준설토 처리비(백만원)', header: { text: '준설토\n처리비\n(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'DRDG_ETND_SM', fieldName: '처리량 계(톤)', header: { text: '처리량 계\n(톤)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RCLM', fieldName: '매립', header: { text: '매립' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OCEAN_DPSL', fieldName: '해양투기', header: { text: '해양투기' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RUSE', fieldName: '재활용', header: { text: '재활용' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'ETC', fieldName: '기타(준설)', header: { text: '기타\n(소각, 고형화,\n시멘트원료 등)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgAcmsltUntpc', fieldName: 'drdgAcmsltUntpc', header: { text: '준설토\n처리비\n(백만원)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drdgEtndSm', fieldName: 'drdgEtndSm', header: { text: '처리량 계\n(톤)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rclm', fieldName: 'rclm', header: { text: '매립' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'oceanDpsl', fieldName: 'oceanDpsl', header: { text: '해양투기' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'ruse', fieldName: 'ruse', header: { text: '재활용' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'etc', fieldName: 'etc', header: { text: '기타' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, /* 7. 기타 및 의견 */ - { name: 'RMK_DRDG', fieldName: '비고(준설)', header: { text: '비고' } }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'YR_GBN', 'PIPL_FCLTY_EXTN', + { name: 'addrGroup', header: { text: '행정구역' }, items: [{ column: 'sido', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, 'gugun'] }, + 'displayYear', 'piplFcltyExtn', { name: 'drdgGroup', header: { text: '준설연장 및 개소수' }, items: [ { name: 'pipeLenGroup', header: { text: '관로(m)' }, - items: ['DRDG_PIPE_SM', 'DRDG_CMBN_PIPE', { name: 'sepRepairGroup', header: { text: '분류식' }, items: ['DRDG_RAINWTR_PIPE', 'DRDG_PLTWTR_PIPE'] }] + items: ['drdgPipeSm', 'drdgCmbnPipe', { name: 'sepRepairGroup', header: { text: '분류식' }, items: ['drdgRainwtrPipe', 'drdgPltwtrPipe'] }] }, - 'MNHLE_SITE_DRDG', 'PLTWTR_RCVR_SITE_DRDG', 'SOILSPLL_SOILHOLE_SITE' + 'mnhleSite', 'pltwtrRcvrSite', 'soilspllSoilholeSite' ] }, - 'DRDG_TRGET_EXTN', 'DRDG_TRGET_RT', 'PIPL_FCLTY_RT', - { name: 'acmsltGroup', header: { text: '준설실적' }, items: ['DRDG_ACMSLT_AMOUNT', 'DRDG_ACMSLT_QY'] }, + 'drdgTrgetExtn', 'drdgTrgetRt', 'piplFcltyRt', + { name: 'acmsltGroup', header: { text: '준설실적' }, items: ['drdgAcmsltAmount', 'drdgAcmsltQy'] }, { name: 'processGroup', header: { text: '준설토사처리' }, - items: ['DRDG_ACMSLT_UNTPC', 'DRDG_ETND_SM', 'RCLM', 'OCEAN_DPSL', 'RUSE', 'ETC'] + items: ['drdgAcmsltUntpc', 'drdgEtndSm', 'rclm', 'oceanDpsl', 'ruse', 'etc'] }, - 'RMK_DRDG' + 'rmk' ] } ]; @@ -132,6 +278,7 @@ export const SewagePipeMaintainColumnInfoList = rawMaintainList.map(tab => ({ ...tab, columns: tab.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: "rg-cell-readonly" })) })); \ 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 7bcb3a41f..1020ce3bc 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -1,96 +1,80 @@ import { ValueType } from "realgrid"; const baseYear = new Date().getFullYear(); import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; -import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; +import type { GridView } from "@/components/type/DataGrid.type"; // 하수도 통계 관리 02. 하수관로 const rawList = [ { - tabKey: 'a', + tabKey: 'ST00020001', tabLabel: '지역별 시설현황', rowStartNum: 4, fixedColumns: 2, columns: [ { - name: 'sido', - fieldName: 'sido', + name: 'ctpvNm', + fieldName: 'ctpvNm', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { 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)); - } + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); } const divisionValues = Array.from(uniqueValues); - if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; - return '합계'; + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; } }, { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { 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)); - } + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); } 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: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { 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)); - } + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); } const divisionValues = Array.from(uniqueValues); return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; } }, { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { 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)); - } + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); } const divisionValues = Array.from(uniqueValues); return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; @@ -101,14 +85,14 @@ const rawList = [ /* 하수정비 기본계획 */ { 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: 'swrgPipeMntnPlanExtn', fieldName: 'swrgPipeMntnPlanExtn', header: { text: '총관로정비\n계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'swrgPipePlanExtn', fieldName: 'swrgPipePlanExtn', header: { text: '총계획연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'swrgPipeFcltyExtn', fieldName: 'swrgPipeFcltyExtn', header: { text: '총시설연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'swrgPipePsplyRt', fieldName: 'swrgPipePsplyRt', 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: 'cmbnMntnPlanAr', fieldName: 'cmbnMntnPlanAr', header: { text: '관로정비\n계획면적(㎢)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'cmbnMntnPlanExtn', fieldName: 'cmbnMntnPlanExtn', 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' }, @@ -117,50 +101,50 @@ const rawList = [ { 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: 'pltwtrMntnPlanAr', fieldName: 'pltwtrMntnPlanAr', header: { text: '관로정비\n계획면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'pltwtrMntnPlanExtn', fieldName: 'pltwtrMntnPlanExtn', 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: 'pltwtrFcltyExtn', fieldName: 'pltwtrFcltyExtn', header: { text: '시설연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'pltwtrRctglExtn', fieldName: 'pltwtrRctglExtn', header: { text: '사각형(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'pltwtrCicleExtn', fieldName: 'pltwtrCicleExtn', 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: 'rainwtrMntnPlanAr', fieldName: 'rainwtrMntnPlanAr', header: { text: '관로정비\n계획면적(㎢)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'rainwtrMntnPlanExtn', fieldName: 'rainwtrMntnPlanExtn', 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: 'rainwtrFcltyExtn', fieldName: 'rainwtrFcltyExtn', header: { text: '시설연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrRctglExtn', fieldName: 'rainwtrRctglExtn', header: { text: '사각형(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rainwtrCicleExtn', fieldName: 'rainwtrCicleExtn', 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: 'cmbnMnhleCnt', fieldName: 'cmbnMnhleCnt', header: { text: '합류식맨홀(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrMnhleCnt', fieldName: 'pltwtrMnhleCnt', header: { text: '오수맨홀(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrMnhleCnt', fieldName: 'rainwtrMnhleCnt', header: { text: '우수맨홀(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrRcvrCnt', fieldName: 'pltwtrRcvrCnt', header: { text: '오수받이(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrRcvrCnt', fieldName: 'rainwtrRcvrCnt', header: { text: '우수받이(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrChmbrCnt', fieldName: 'rainwtrChmbrCnt', header: { text: '우수토실(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrOutltCnt', fieldName: 'rainwtrOutltCnt', 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: '비고' } }, + { name: 'swrgRegstrCmptExtn', fieldName: 'swrgRegstrCmptExtn', header: { text: '하수도대장전산화연장(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'swrgRegstrCmptRt', fieldName: 'swrgRegstrCmptRt', header: { text: '하수도대장전산화율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, ], columnLayout: [ { name: 'areaGroup', header: { text: '행정구역' }, items: [ - { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, - 'gugun', - 'adstDong', + { column: 'ctpvNm', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'sggNm', + 'admdstNm', ] }, 'displayYear', - 'targetYr', 'piplImprmnPlanExtn', 'piplPlanExtn', 'piplFcltyExtn', 'piplSupprt', + 'targetYr', 'swrgPipeMntnPlanExtn', 'swrgPipePlanExtn', 'swrgPipeFcltyExtn', 'swrgPipePsplyRt', { name: 'combinedGroup', header: { text: '합류식(m)' }, - items: ['cmbnPlanAr', 'cmbnImprmnPlanExtn', 'cmbnPlanExtn', 'cmbnFcltyExtn', + items: ['cmbnMntnPlanAr', 'cmbnMntnPlanExtn', 'cmbnPlanExtn', 'cmbnFcltyExtn', { name: 'cmbnAmGroup', header: { text: '암거' }, items: ['cmbnRctglExtn', 'cmbnCicleExtn'] }, 'cmbnOpswrExtn', 'cmbnSdgttrExtn' ], @@ -170,253 +154,408 @@ const rawList = [ items: [ { name: 'foulPipeGroup', header: { text: '오수관로' }, - items: ['pltwtrPlanAr', 'pltwtrImprmnPlanExtn', 'pltwtrPlanExtn', 'pltwtrFlctyExtn', - { name: 'foulAmGroup', header: { text: '암거' }, items: ['pltwtrSqreExtn', 'pltwtrCiclExtn'] }] + items: ['pltwtrMntnPlanAr', 'pltwtrMntnPlanExtn', 'pltwtrPlanExtn', 'pltwtrFcltyExtn', + { name: 'foulAmGroup', header: { text: '암거' }, items: ['pltwtrRctglExtn', 'pltwtrCicleExtn'] }] }, { name: 'rainPipeGroup', header: { text: '우수관로' }, - items: ['rainwtrPlanAr', 'rainwtrImprmnPlanExtn', 'rainwtrPlanExtn', 'rainwtrFlctyExtn', - { name: 'rainAmGroup', header: { text: '암거' }, items: ['rainwtrSqreExtn', 'rainwtrCiclExtn'] }, + items: ['rainwtrMntnPlanAr', 'rainwtrMntnPlanExtn', 'rainwtrPlanExtn', 'rainwtrFcltyExtn', + { name: 'rainAmGroup', header: { text: '암거' }, items: ['rainwtrRctglExtn', 'rainwtrCicleExtn'] }, 'rainwtrOpswrExtn', 'rainwtrSdgttrExtn'] } ], }, - 'mnhleSite', - { name: 'fcltyGroup', header: { text: '부속시설(개소)' }, items: ['pltwtrMnhleSite', 'rainwtrMnhleSite'] }, - 'pltwtrRcvrSite', 'rainwtrRcvrSite', 'soilspllSite', 'soilholeSite', 'sewerRegstrCmptExtn', 'sewerRegstrCmptRt', 'rmk' + 'cmbnMnhleCnt', + { name: 'fcltyGroup', header: { text: '부속시설(개소)' }, items: ['pltwtrMnhleCnt', 'rainwtrMnhleCnt'] }, + 'pltwtrRcvrCnt', 'rainwtrRcvrCnt', 'rainwtrChmbrCnt', 'rainwtrOutltCnt', 'swrgRegstrCmptExtn', 'swrgRegstrCmptRt', 'rmrk' ] }, - { - 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; - 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: '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: '구분' }, - 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: 'fctCpc', fieldName: 'fctCpc', header: { text: '하수처리\n시설용량\n(㎥/일)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - /* 처리구역면적(㎢) */ - { 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' }, - - /* 배수설비(개소) */ - { 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: '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: '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' }, - - /* 지선(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: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, - ], - columnLayout: [ - { - name: 'addrGroup', - header: { text: '행정구역' }, - items: [ - { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, - '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', + tabKey: 'ST00020002', + tabLabel: '시설별 설치현황', + rowStartNum: 4, + fixedColumns: 3, + columns: [ + { + name: 'ctpvNm', + fieldName: 'ctpvNm', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) => { + 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: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true }, + { name: 'swrmtFcltyNm', fieldName: 'swrmtFcltyNm', header: { text: '하수처리시설 명' }, mergeCells: true, mergeGroupField: 'admdstNm' }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + return Array.from(uniqueValues)[divisionIndex] ? Array.from(uniqueValues)[divisionIndex] + ' 합계' : ''; + } + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + return Array.from(uniqueValues)[divisionIndex] ? Array.from(uniqueValues)[divisionIndex] + ' 합계' : ''; + } + } + ] + }, + { name: 'swrmtFcltyCpc', fieldName: 'swrmtFcltyCpc', header: { text: '하수처리\n시설용량\n(㎥/일)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'procZoneArSm', fieldName: 'procZoneArSm', 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: 'sprsysProcZoneAr', fieldName: 'sprsysProcZoneAr', header: { text: '분류식' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { 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: 'sprsysDrngEqpSm', fieldName: 'sprsysDrngEqpSm', header: { text: '분류식 합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'swrgPipeFcltyExtn', fieldName: 'swrgPipeFcltyExtn', header: { text: '총\n시설연장\n(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'icgPltwtrMnnSm', fieldName: 'icgPltwtrMnnSm', 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: 'pltwtrMnnExtn', fieldName: 'pltwtrMnnExtn', header: { text: '오수간선' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnRainwtrChmbrCnt', fieldName: 'cmbnRainwtrChmbrCnt', header: { text: '우수토실(합류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysRainwtrChmbrCnt', fieldName: 'sprsysRainwtrChmbrCnt', header: { text: '우수토실(분류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysRainwtrOutltCnt', fieldName: 'sprsysRainwtrOutltCnt', header: { text: '우수토구(분류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { 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: 'sprsysExlnBrnchlnExtn', fieldName: 'sprsysExlnBrnchlnExtn', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'sprsysDrtwBrnchlnExtn', fieldName: 'sprsysDrtwBrnchlnExtn', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, + ], + columnLayout: [ + { + name: 'addrGroup', + header: { text: '행정구역' }, + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'sggNm', + 'admdstNm', + 'swrmtFcltyNm', + ] + }, + 'swrmtFcltyCpc', + 'displayYear', + { name: 'areaGroup', header: { text: '처리구역면적(㎢)' }, items: ['procZoneArSm', 'cmbnProcZoneAr', 'sprsysProcZoneAr'] }, + { + name: 'drainGroup', header: { text: '배수설비(개소)' }, + items: ['drngEqpSm', 'cmbnDrngEqp', 'sprsysDrngEqpSm'], + }, + { + name: 'pipeGroup', header: { text: '하수관로' }, + items: [ + 'swrgPipeFcltyExtn', + { name: 'trunkGroup', header: { text: '차집/오수간선(m)' }, items: ['icgPltwtrMnnSm', 'icgExtn', 'pltwtrMnnExtn'] }, + { + name: 'siteGroup', header: { text: '토실/토구(개소)' }, + items: [ + { name: 'cmbnSiteGroup', header: { text: '합류식' }, items: ['cmbnRainwtrChmbrCnt'] }, + { name: 'sepSiteGroup', header: { text: '분류식' }, items: ['sprsysRainwtrChmbrCnt', 'sprsysRainwtrOutltCnt'] } + ] + }, + { + name: 'branchGroup', header: { text: '지선(m)' }, + items: ['brnchlnSm', 'cmbnBrnchlnExtn', { name: 'sepBranchGroup', header: { text: '우수/오수' }, items: ['sprsysExlnBrnchlnExtn', 'sprsysDrtwBrnchlnExtn'] }] + } + ], + }, + 'rmrk' + ] + }, + { + tabKey: 'ST00020003', tabLabel: '관종별 현황', rowStartNum: 2, fixedColumns: 3, columns: [ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'SWRG_PIPE_NM', fieldName: '관종', header: { text: '관종' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + } + ] + }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + } + ] + }, - { name: 'TOTAL_EXTN_SUM', fieldName: '계(총계)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'LAYING_EXTN_SUM', fieldName: '매설연장(m)_총계', header: { text: '매설연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'REMVL_EXTN_SUM', fieldName: '철거연장(m)_총계', header: { text: '철거연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + /* 총계 필드 */ + { name: 'totalExtnSum', fieldName: 'totalExtnSum', header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'layingExtnSum', fieldName: 'layingExtnSum', header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'remvlExtnSum', fieldName: 'remvlExtnSum', header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, ...Array.from({ length: 30 }, (_, i) => { const yNum = (30 - i).toString().padStart(2, '0'); - const yrOffset = 29 - i; + // const yrOffset = 29 - i; return [ - { name: `TOTAL_EXTN_${yNum}Y`, fieldName: `계(-${yrOffset}년)`, header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: `LAYING_EXTN_${yNum}Y`, fieldName: `매설연장(m)_(-${yrOffset}년)`, header: { text: '매설연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: `REMVL_EXTN_${yNum}Y`, fieldName: `철거연장(m)_(-${yrOffset}년)`, header: { text: '철거연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' } + { name: `totalExtn${yNum}y`, fieldName: `totalExtn${yNum}y`, header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: `layingExtn${yNum}y`, fieldName: `layingExtn${yNum}y`, header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: `remvlExtn${yNum}y`, fieldName: `remvlExtn${yNum}y`, header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' } ]; }).flat(), - { name: 'LAYING_EXTN', fieldName: `매설연장(m)_당해년도`, header: { text: '매설연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { name: 'layingExtn', fieldName: 'layingExtn', header: { text: '매설연장(m)\n(당해년도)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'SWRG_PIPE_NM', 'YR_GBN', - { name: 'summaryGroup', header: { text: '총계' }, items: ['TOTAL_EXTN_SUM', 'LAYING_EXTN_SUM', 'REMVL_EXTN_SUM'] }, + { + name: 'areaGroup', + header: { text: '행정구역' }, + items: [ + + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', + 'swrgPipeNm', + ] + }, + 'displayYear', + { name: 'summaryGroup', header: { text: '총계' }, items: ['totalExtnSum', 'layingExtnSum', 'remvlExtnSum'] }, + ...Array.from({ length: 30 }, (_, i) => { const yNum = (30 - i).toString().padStart(2, '0'); const yrOffset = 29 - i; - return { name: `yr${yNum}Group`, header: { text: `-${yrOffset}년` }, items: [`TOTAL_EXTN_${yNum}Y`, `LAYING_EXTN_${yNum}Y`, `REMVL_EXTN_${yNum}Y`] }; + return { + name: `yr${yNum}Group`, + header: { text: `-${yrOffset}년` }, + items: [`totalExtn${yNum}y`, `layingExtn${yNum}y`, `remvlExtn${yNum}y`] + }; }), - { name: 'currentYrGroup', header: { text: `${baseYear}년` }, items: ['LAYING_EXTN'] }, - 'RMK' + + { name: 'currentYrGroup', header: { text: `${baseYear}년` }, items: ['layingExtn'] }, + 'rmk' ] }, { - tabKey: 'd', + tabKey: 'ST00020004', tabLabel: '관경별 현황', rowStartNum: 2, fixedColumns: 3, columns: [ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '구군' } }, - { name: 'SWRG_PIPE_NM', fieldName: '관종', header: { text: '관종' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, - { name: 'TOTAL_EXTN_M', fieldName: '총계(m)', header: { text: '총계(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'UNDER_200', fieldName: '200mm 미만', header: { text: '200mm 미만' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OVER_200_UNDEV_300', fieldName: '200mm이상~300mm미만', header: { text: '200mm이상~\n300mm미만' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OVER_300_UNDEV_500', fieldName: '300mm이상~500mm미만', header: { text: '300mm이상~\n500mm미만' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OVER_500_UNDEV_800', fieldName: '500mm이상~800mm미만', header: { text: '500mm이상~\n800mm미만' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OVER_800', fieldName: '800mm 이상', header: { text: '800mm 이상' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'BOX', fieldName: '사각형(m)', header: { text: '사각형(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'ETC', fieldName: '기타(측구/개거 등)', header: { text: '기타\n(측구/개거 등)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) => { + 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: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) => { + 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: 'totalExtnM', fieldName: 'totalExtnM', header: { text: '총계(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'under200', fieldName: 'under200', header: { text: '200mm 미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over200Undev300', fieldName: 'over200Undev300', header: { text: '200mm이상~\n300mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over300Undev500', fieldName: 'over300Undev500', header: { text: '300mm이상~\n500mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over500Undev500', fieldName: 'over500Undev500', header: { text: '500mm이상~\n800mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over800', fieldName: 'over800', header: { text: '800mm 이상' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'box', fieldName: 'box', header: { text: '사각형(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'etc', fieldName: 'etc', header: { text: '기타\n(측구/개거 등)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'SWRG_PIPE_NM', 'YR_GBN', 'TOTAL_EXTN_M', - { name: 'circleGroup', header: { text: '원형(m)' }, items: ['UNDER_200', 'OVER_200_UNDEV_300', 'OVER_300_UNDEV_500', 'OVER_500_UNDEV_800', 'OVER_800'] }, - 'BOX', 'ETC', 'RMK', + { + name: 'addrGroup', + header: { text: '행정구역' }, + items: [ + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', + 'swrgPipeNm', + ] + }, + 'displayYear', + 'totalExtnM', + { + name: 'circleGroup', + header: { text: '원형(m)' }, + items: ['under200', 'over200Undev300', 'over300Undev500', 'over500Undev500', 'over800'] + }, + 'box', + 'etc', + 'rmk' ] } ]; @@ -425,6 +564,7 @@ export const SewagePipeColumnInfoListRvw = rawList.map(tab => ({ ...tab, columns: tab.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: "rg-cell-readonly" })) })); \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts index 43aa1c62d..83d0ab65a 100644 --- a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts @@ -1,186 +1,332 @@ import { ValueType } from "realgrid"; +import { + footerIntArrayByDivision, + footerDecimal1ArrayByDivision, + footerDecimal3ArrayByDivision +} from "@/components/util/grid/footerSummary"; +import type { GridView } from "@/components/type/DataGrid.type"; + // 하수도 통계 관리 04. 하수저류 시설 및 빗물 펌프장 const rawReservoirList = [ { - tabKey: 'a', - tabLabel: '하수저류시설', + tabKey: 'ST00040001', + tabLabel: '하수저류시설현황', rowStartNum: 3, fixedColumns: 2, columns: [ /* 1. 기본 정보 */ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'SWGE_STRGE_NM', fieldName: '시설명', header: { text: '시설명' } }, - { name: 'ADDR', fieldName: '위치', header: { text: '위치' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, - - /* 2. 시설 제원 */ - { name: 'RN_DT', fieldName: '가동개시 년월일', header: { text: '가동개시\n년월일' } }, - { name: 'PL_DSGN_FRQNC', fieldName: '계획설계빈도', header: { text: '계획설계빈도' } }, - { name: 'INS_PER1', fieldName: '침수예방', header: { text: '침수예방' } }, - { name: 'INS_PER2', fieldName: '수질오염저감', header: { text: '수질오염저감' } }, - { name: 'INS_PER3', fieldName: '하수 재이용 용도', header: { text: '하수\n재이용\n용도' } }, - { name: 'FAC_RES', fieldName: '시설구조', header: { text: '시설구조(일반지하식,지하터널식,기타)' } }, - { name: 'DGR_AR', fieldName: '집수구역면적(㎢)', header: { text: '집수구역면적(㎢)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'SWGE_SGBS_CPCTY', fieldName: '시설용량(㎥)', header: { text: '시설용량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - /* 3. 펌프시설 */ - { name: 'TOT_CPCTY', fieldName: '총양수량(㎥/min)', header: { text: '총양수량\n(㎥/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CALBR', fieldName: '구경(mm)_1', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY', fieldName: '대당 양수량_1', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO', fieldName: '펌프대수_1', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO', fieldName: '예비펌프대수_1', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_CALBR2', fieldName: '구경(mm)_2', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY2', fieldName: '대당 양수량_2', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO2', fieldName: '펌프대수_2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO2', fieldName: '예비펌프대수_2', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_CALBR3', fieldName: '구경(mm)_3', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY3', fieldName: '대당 양수량_3', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO3', fieldName: '펌프대수_3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO3', fieldName: '예비펌프대수_3', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - - /* 4. 부대시설 및 방식 */ - { name: 'FRE_FNNC', fieldName: '전처리시설(유/무)', header: { text: '전처리시설(유/무)' } }, - { name: 'DORZEQP_FNNC', fieldName: '탈취설비(유/무)', header: { text: '탈취설비(유/무)' } }, - { name: 'SELF_ETRQ_FNNC', fieldName: '자가발전(유/무)', header: { text: '자가발전(유/무)' } }, - { name: 'DRA_MTH', fieldName: '배수방식(자연배수,강제배수)', header: { text: '배수방식(자연배수,강제배수)' } }, - { name: 'ELEC_MTH', fieldName: '저류방식(직렬,병렬)', header: { text: '저류방식(직렬,병렬)' } }, - { name: 'ELEC_PRO_MAT', fieldName: '저류수 처리방법', header: { text: '저류수\n처리방법' } }, - { name: 'DIS_WAT', fieldName: '방류수역(또는 연계처리장명)', header: { text: '방류수역(또는 연계처리장명)' } }, - - /* 5. 최종처분량 및 방법 */ - { name: 'SDMT_TRE_CPCTY', fieldName: '침사(량)', header: { text: '침사' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'IPRT_TRE_CPCTY', fieldName: '협잡물(량)', header: { text: '협잡물' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SDMT_TRE_MTH', fieldName: '침사(방법)', header: { text: '침사' } }, - { name: 'IPRT_TRE_MTH', fieldName: '협잡물(방법)', header: { text: '협잡물' } }, - - /* 6. 운영실적 */ - { name: 'YRSTP_RN_DYN', fieldName: '가동일수', header: { text: '가동일수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'DSPLCMNT_AMNT', fieldName: '총배수량(㎥/년)', header: { text: '총배수량\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, - ], - columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'SWGE_STRGE_NM', 'ADDR', 'YR_GBN', 'RN_DT', 'PL_DSGN_FRQNC', { - name: 'purposeGroup', - header: { text: '설치목적(O,X로 표기, 중복체크 가능)' }, - items: ['INS_PER1', 'INS_PER2', 'INS_PER3'] - }, - 'FAC_RES', 'DGR_AR', 'SWGE_SGBS_CPCTY', - { - name: 'pumpMainGroup', - header: { text: '펌프시설' }, - items: [ - 'TOT_CPCTY', - { name: 'p1Group', header: { text: '펌프시설1' }, items: ['PUMP_CALBR', 'WP_CPCTY', 'PUMP_CO', 'PUMP_PREPAR_CO'] }, - { name: 'p2Group', header: { text: '펌프시설2' }, items: ['PUMP_CALBR2', 'WP_CPCTY2', 'PUMP_CO2', 'PUMP_PREPAR_CO2'] }, - { name: 'p3Group', header: { text: '펌프시설3' }, items: ['PUMP_CALBR3', 'WP_CPCTY3', 'PUMP_CO3', 'PUMP_PREPAR_CO3'] } + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) => { + 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, sortdisable: true }, + { name: 'swgeStrgeNm', fieldName: 'swgeStrgeNm', header: { text: '시설명' }, mergeCells: true }, + { name: 'addr', fieldName: 'addr', header: { text: '위치' }, mergeCells: true}, { - name: 'subFacGroup', - header: { text: '부대시설' }, - items: ['FRE_FNNC', 'DORZEQP_FNNC', 'SELF_ETRQ_FNNC'] + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) => { + 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] + '합계' : ''; + } + } + ] }, - 'DRA_MTH', 'ELEC_MTH', 'ELEC_PRO_MAT', 'DIS_WAT', + + /* 2. 시설 제원 */ + { name: 'rnDt', fieldName: 'rnDt', header: { text: '가동개시 년월일' } }, + { name: 'plDsgnFrqnc', fieldName: 'plDsgnFrqnc', header: { text: '계획설계빈도' } }, + { name: 'insPer1', fieldName: 'insPer1', header: { text: '침수예방' } }, + { name: 'insPer2', fieldName: 'insPer2', header: { text: '수질오염저감' } }, + { name: 'insPer3', fieldName: 'insPer3', header: { text: '하수 재이용 용도' } }, + { name: 'facRes', fieldName: 'facRes', header: { text: '시설구조' } }, + { name: 'dgrAr', fieldName: 'dgrAr', header: { text: '집수구역면적(㎢)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000', footer: footerDecimal3ArrayByDivision }, + { name: 'swgeSgbsCpcty', fieldName: 'swgeSgbsCpcty', header: { text: '시설용량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + + /* 3. 펌프시설 */ + { name: 'totCpcty', fieldName: 'totCpcty', header: { text: '총양수량(㎥/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCalbr', fieldName: 'pumpCalbr', header: { text: '구경(mm)' } }, + { name: 'wpCpcty', fieldName: 'wpCpcty', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo', fieldName: 'pumpCo', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo', fieldName: 'pumpPreparCo', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpCalbr2', fieldName: 'pumpCalbr2', header: { text: '구경(mm)' } }, + { name: 'wpCpcty2', fieldName: 'wpCpcty2', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo2', fieldName: 'pumpCo2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo2', fieldName: 'pumpPreparCo2', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpCalbr3', fieldName: 'pumpCalbr3', header: { text: '구경(mm)' } }, + { name: 'wpCpcty3', fieldName: 'wpCpcty3', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo3', fieldName: 'pumpCo3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo3', fieldName: 'pumpPreparCo3', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + + /* 4. 부대시설 및 방식 */ + { name: 'freFnnc', fieldName: 'freFnnc', header: { text: '전처리시설(유/무)' } }, + { name: 'dorzeqpFnnc', fieldName: 'dorzeqpFnnc', header: { text: '탈취설비(유/무)' } }, + { name: 'selfEtrqFnnc', fieldName: 'selfEtrqFnnc', header: { text: '자가발전(유/무)' } }, + { name: 'draMth', fieldName: 'draMth', header: { text: '배수방식' } }, + { name: 'elecMth', fieldName: 'elecMth', header: { text: '저류방식' } }, + { name: 'elecProMat', fieldName: 'elecProMat', header: { text: '저류수 처리방법' } }, + { name: 'disWat', fieldName: 'disWat', header: { text: '방류수역' } }, + + /* 5. 최종처분량 및 방법 */ + { name: 'sdmtTreCpcty', fieldName: 'sdmtTreCpcty', header: { text: '침사' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'iprtTreCpcty', fieldName: 'iprtTreCpcty', header: { text: '협잡물' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'sdmtTreMth', fieldName: 'sdmtTreMth', header: { text: '침사' } }, + { name: 'iprtTreMth', fieldName: 'iprtTreMth', header: { text: '협잡물' } }, + + /* 6. 운영실적 */ + { name: 'yrstpRnDyn', fieldName: 'yrstpRnDyn', header: { text: '가동일수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'dsplcmntAmnt', fieldName: 'dsplcmntAmnt', header: { text: '총배수량(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ { - name: 'disposalAmtGroup', - header: { text: '최종처분량(ton/년)' }, - items: ['SDMT_TRE_CPCTY', 'IPRT_TRE_CPCTY'] + name: 'areaGroup', header: { text: '행정구역' }, items: [ + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', 'swgeStrgeNm', 'addr' + ] }, + 'displayYear', 'rnDt', 'plDsgnFrqnc', + { name: 'purposeGroup', header: { text: '설치목적' }, items: ['insPer1', 'insPer2', 'insPer3'] }, + 'facRes', 'dgrAr', 'swgeSgbsCpcty', { - name: 'disposalMthGroup', - header: { text: '최종처분방법' }, - items: ['SDMT_TRE_MTH', 'IPRT_TRE_MTH'] + name: 'pumpMainGroup', header: { text: '펌프시설' }, + items: [ + 'totCpcty', + { name: 'p1Group', header: { text: '펌프시설1' }, items: ['pumpCalbr', 'wpCpcty', 'pumpCo', 'pumpPreparCo'] }, + { name: 'p2Group', header: { text: '펌프시설2' }, items: ['pumpCalbr2', 'wpCpcty2', 'pumpCo2', 'pumpPreparCo2'] }, + { name: 'p3Group', header: { text: '펌프시설3' }, items: ['pumpCalbr3', 'wpCpcty3', 'pumpCo3', 'pumpPreparCo3'] } + ] }, - { - name: 'opGroup', - header: { text: '운영실적' }, - items: ['YRSTP_RN_DYN', 'DSPLCMNT_AMNT'] - }, - 'RMK' + { name: 'subFacGroup', header: { text: '부대시설' }, items: ['freFnnc', 'dorzeqpFnnc', 'selfEtrqFnnc'] }, + 'draMth', 'elecMth', 'elecProMat', 'disWat', + { name: 'disposalAmtGroup', header: { text: '최종처분량(ton/년)' }, items: ['sdmtTreCpcty', 'iprtTreCpcty'] }, + { name: 'disposalMthGroup', header: { text: '최종처분방법' }, items: ['sdmtTreMth', 'iprtTreMth'] }, + { name: 'opGroup', header: { text: '운영실적' }, items: ['yrstpRnDyn', 'dsplcmntAmnt'] }, + 'rmk' ] }, - { - tabKey: 'b', +{ + tabKey: 'ST00040002', tabLabel: '빗물펌프장', rowStartNum: 3, fixedColumns: 2, columns: [ /* 1. 기본 정보 */ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'SWGE_STRGE_NM', fieldName: '시설명', header: { text: '시설명' } }, - { name: 'ADDR', fieldName: '위치', header: { text: '위치' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, - - /* 2. 시설 제원 */ - { name: 'FCT_KND', fieldName: '시설물종류', header: { text: '시설물종류' } }, - { name: 'RN_DT', fieldName: '가동개시 년월일', header: { text: '가동개시\n년월일' } }, - { name: 'PL_DSGN_FRQNC', fieldName: '계획설계빈도', header: { text: '계획설계빈도' } }, - { name: 'DGR_AR', fieldName: '집수구역면적(㎢)', header: { text: '집수구역면적(㎢)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - - /* 3. 유수지 및 흡수정 */ - { name: 'REV_FNNC', fieldName: '유수지유무(유,무)', header: { text: '유수지유무\n(유,무)' } }, - { name: 'REV_CPCTY', fieldName: '저류량(유수지)', header: { text: '저류량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'ABS_CPCTY', fieldName: '저류량(흡수정)', header: { text: '저류량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'SWGE_SGBS_CPCTY', fieldName: '시설용량(㎥)', header: { text: '시설용량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - /* 4. 펌프시설 */ - { name: 'TOT_CPCTY', fieldName: '총양수량(㎥/min)', header: { text: '총양수량\n(㎥/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CALBR', fieldName: '구경(mm)_1', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY', fieldName: '대당 양수량_1', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO', fieldName: '펌프대수_1', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO', fieldName: '예비펌프대수_1', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_CALBR2', fieldName: '구경(mm)_2', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY2', fieldName: '대당 양수량_2', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO2', fieldName: '펌프대수_2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO2', fieldName: '예비펌프대수_2', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_CALBR3', fieldName: '구경(mm)_3', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY3', fieldName: '대당 양수량_3', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO3', fieldName: '펌프대수_3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO3', fieldName: '예비펌프대수_3', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - - /* 5. 부대시설 및 기타 */ - { name: 'FRE_FNNC', fieldName: '전처리시설', header: { text: '전처리시설' } }, - { name: 'DORZEQP_FNNC', fieldName: '탈취설비', header: { text: '탈취설비' } }, - { name: 'SELF_ETRQ_FNNC', fieldName: '자가발전', header: { text: '자가발전' } }, - { name: 'DIS_WAT', fieldName: '방류수역', header: { text: '방류수역' } }, - - /* 6. 최종처분량 및 방법 */ - { name: 'SDMT_TRE_CPCTY', fieldName: '침사(량)', header: { text: '침사(량)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'IPRT_TRE_CPCTY', fieldName: '협잡물(량)', header: { text: '협잡물(량)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SDMT_TRE_MTH', fieldName: '침사(방법)', header: { text: '침사(방법)' } }, - { name: 'IPRT_TRE_MTH', fieldName: '협잡물(방법)', header: { text: '협잡물(방법)' } }, - - /* 7. 운영실적 */ - { name: 'YRSTP_RN_DYN', fieldName: '가동일수', header: { text: '가동일수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'DSPLCMNT_AMNT', fieldName: '총배수량(㎥/년)', header: { text: '총배수량\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) => { + 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, sortdisable: true }, + { name: 'swgeStrgeNm', fieldName: 'swgeStrgeNm', header: { text: '시설명' }, mergeCells: true }, + { name: 'addr', fieldName: 'addr', header: { text: '위치' }, mergeCells: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + 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) => { + 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: 'fctKnd', fieldName: 'fctKnd', header: { text: '시설물종류' } }, + { name: 'rnDt', fieldName: 'rnDt', header: { text: '가동개시 년월일' } }, + { name: 'plDsgnFrqnc', fieldName: 'plDsgnFrqnc', header: { text: '계획설계빈도' } }, + { name: 'dgrAr', fieldName: 'dgrAr', header: { text: '집수구역면적(㎢)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000', footer: footerDecimal3ArrayByDivision }, + { name: 'revFnnc', fieldName: 'revFnnc', header: { text: '유수지유무\n(유,무)' } }, + { name: 'revCpcty', fieldName: 'revCpcty', header: { text: '저류량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'absCpcty', fieldName: 'absCpcty', header: { text: '저류량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'swgeSgbsCpcty', fieldName: 'swgeSgbsCpcty', header: { text: '시설용량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'totCpcty', fieldName: 'totCpcty', header: { text: '총양수량(㎥/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCalbr', fieldName: 'pumpCalbr', header: { text: '구경(mm)' } }, + { name: 'wpCpcty', fieldName: 'wpCpcty', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo', fieldName: 'pumpCo', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo', fieldName: 'pumpPreparCo', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpCalbr2', fieldName: 'pumpCalbr2', header: { text: '구경(mm)' } }, + { name: 'wpCpcty2', fieldName: 'wpCpcty2', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo2', fieldName: 'pumpCo2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo2', fieldName: 'pumpPreparCo2', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpCalbr3', fieldName: 'pumpCalbr3', header: { text: '구경(mm)' } }, + { name: 'wpCpcty3', fieldName: 'wpCpcty3', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo3', fieldName: 'pumpCo3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo3', fieldName: 'pumpPreparCo3', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'freFnnc', fieldName: 'freFnnc', header: { text: '전처리시설' } }, + { name: 'dorzeqpFnnc', fieldName: 'dorzeqpFnnc', header: { text: '탈취설비' } }, + { name: 'selfEtrqFnnc', fieldName: 'selfEtrqFnnc', header: { text: '자가발전' } }, + { name: 'disWat', fieldName: 'disWat', header: { text: '방류수역' } }, + { name: 'sdmtTreCpcty', fieldName: 'sdmtTreCpcty', header: { text: '침사(량)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'iprtTreCpcty', fieldName: 'iprtTreCpcty', header: { text: '협잡물(량)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'sdmtTreMth', fieldName: 'sdmtTreMth', header: { text: '침사(방법)' } }, + { name: 'iprtTreMth', fieldName: 'iprtTreMth', header: { text: '협잡물(방법)' } }, + { name: 'yrstpRnDyn', fieldName: 'yrstpRnDyn', header: { text: '가동일수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'dsplcmntAmnt', fieldName: 'dsplcmntAmnt', header: { text: '총배수량(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'SWGE_STRGE_NM', 'ADDR', 'YR_GBN', 'FCT_KND', 'RN_DT', 'PL_DSGN_FRQNC', 'DGR_AR', - { name: 'revGroup', header: { text: '유수지' }, items: ['REV_FNNC', 'REV_CPCTY'] }, - { name: 'absGroup', header: { text: '흡수정' }, items: ['ABS_CPCTY'] }, - 'SWGE_SGBS_CPCTY', + { + name: 'areaGroup', header: { text: '행정구역' }, items: [ + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', 'swgeStrgeNm', 'addr' + ] + }, + 'displayYear', 'fctKnd', 'rnDt', 'plDsgnFrqnc', 'dgrAr', + { name: 'revGroup', header: { text: '유수지' }, items: ['revFnnc', 'revCpcty'] }, + { name: 'absGroup', header: { text: '흡수정' }, items: ['absCpcty'] }, + 'swgeSgbsCpcty', { name: 'pumpMainGroup', header: { text: '펌프시설' }, items: [ - 'TOT_CPCTY', - { name: 'p1Group', header: { text: '펌프시설1' }, items: ['PUMP_CALBR', 'WP_CPCTY', 'PUMP_CO', 'PUMP_PREPAR_CO'] }, - { name: 'p2Group', header: { text: '펌프시설2' }, items: ['PUMP_CALBR2', 'WP_CPCTY2', 'PUMP_CO2', 'PUMP_PREPAR_CO2'] }, - { name: 'p3Group', header: { text: '펌프시설3' }, items: ['PUMP_CALBR3', 'WP_CPCTY3', 'PUMP_CO3', 'PUMP_PREPAR_CO3'] } + 'totCpcty', + { name: 'p1Group', header: { text: '펌프시설1' }, items: ['pumpCalbr', 'wpCpcty', 'pumpCo', 'pumpPreparCo'] }, + { name: 'p2Group', header: { text: '펌프시설2' }, items: ['pumpCalbr2', 'wpCpcty2', 'pumpCo2', 'pumpPreparCo2'] }, + { name: 'p3Group', header: { text: '펌프시설3' }, items: ['pumpCalbr3', 'wpCpcty3', 'pumpCo3', 'pumpPreparCo3'] } ] }, - { name: 'subFacGroup', header: { text: '부대시설' }, items: ['FRE_FNNC', 'DORZEQP_FNNC', 'SELF_ETRQ_FNNC'] }, - 'DIS_WAT', - { name: 'disposalAmtGroup', header: { text: '최종처분량(ton/년)' }, items: ['SDMT_TRE_CPCTY', 'IPRT_TRE_CPCTY'] }, - { name: 'disposalMthGroup', header: { text: '최종처분방법' }, items: ['SDMT_TRE_MTH', 'IPRT_TRE_MTH'] }, - { name: 'opGroup', header: { text: '운영실적' }, items: ['YRSTP_RN_DYN', 'DSPLCMNT_AMNT'] }, - 'RMK' + { name: 'subFacGroup', header: { text: '부대시설' }, items: ['freFnnc', 'dorzeqpFnnc', 'selfEtrqFnnc'] }, + 'disWat', + { name: 'disposalAmtGroup', header: { text: '최종처분량(ton/년)' }, items: ['sdmtTreCpcty', 'iprtTreCpcty'] }, + { name: 'disposalMthGroup', header: { text: '최종처분방법' }, items: ['sdmtTreMth', 'iprtTreMth'] }, + { name: 'opGroup', header: { text: '운영실적' }, items: ['yrstpRnDyn', 'dsplcmntAmnt'] }, + 'rmk' ] } ]; @@ -189,6 +335,7 @@ export const SewageReservoirPumpInfoListRvw = rawReservoirList.map(tab => ({ ...tab, columns: tab.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: "rg-cell-readonly" })) })); \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts index bf1af2259..25449c6cc 100644 --- a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts @@ -1,89 +1,74 @@ import { ValueType } from "realgrid"; import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; -import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; +import type { GridView } from "@/components/type/DataGrid.type"; -export const SeweragePenetrationRateInfoList = { + +const rawSeweragePenetrationRateInfoList = { 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; - 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: '행정구역명' }, mergeCells: true, editable: false, sortdisable: true }, - { - name: 'displayYear', - fieldName: 'displayYear', - header: { text: '구분' }, - editable: false, + { + name: 'ctpvNm', + fieldName: 'ctpvNm', + header: { text: '시도' }, + mergeCells: true, sortdisable: true, footer: [ { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { 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)); - } + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + } + ] + }, + { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); } const divisionValues = Array.from(uniqueValues); return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; } }, { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { 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)); - } + for (let i = 0; i < dataSource.getRowCount(); i++) { + const val = dataSource.getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); } const divisionValues = Array.from(uniqueValues); return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; @@ -91,39 +76,34 @@ export const SeweragePenetrationRateInfoList = { } ] }, - { name: 'rvbsn', fieldName: 'rvbsn', header: { text: '수계' } }, - { name: 'bstrm', fieldName: 'bstrm', header: { text: '지류' } }, + { name: 'wtsysNm', fieldName: 'rvbsn', header: { text: '수계' } }, + { name: 'trbtrNm', fieldName: 'trbtrNm', header: { text: '지류' } }, { name: 'mdbsn', fieldName: 'mdbsn', header: { text: '세부단위유역' } }, { name: 'lgbsn', fieldName: 'lgbsn', header: { text: '중권역' } }, { name: 'mdmbsn', fieldName: 'mdmbsn', header: { text: '소권역' } }, - { name: 'speclCntrplnAr', fieldName: 'speclCntrplnAr', header: { text: '특별대책지역' } }, - { name: 'popltnSm', fieldName: 'popltnSm', header: { text: '총인구' }, footer: footerIntArrayByDivision, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'arSm', fieldName: 'arSm', header: { text: '총면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, - { name: 'swgeProcZoneInnerSm', fieldName: 'swgeProcZoneInnerSm', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-total' }, - - // 공공하수처리인구 - { name: 'pblswrgPopltnSm', fieldName: 'pblswrgPopltnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'pblswrgPrmTrt', fieldName: 'pblswrgPrmTrt', header: { text: '1차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'pblswrgScdTrt', fieldName: 'pblswrgScdTrt', header: { text: '2차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'pblswrgTertTrt', fieldName: 'pblswrgTertTrt', header: { text: '3차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - - // 폐수처리인구(명) - { name: 'wstrPopltnSm', fieldName: 'wstrPopltnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'wstrPrmTrt', fieldName: 'wstrPrmTrt', header: { text: '1차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'wstrScdTrt', fieldName: 'wstrScdTrt', header: { text: '2차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'wstrTertTrt', fieldName: 'wstrTertTrt', header: { text: '3차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - - { name: 'notConectPopltn', fieldName: 'notConectPopltn', header: { text: '미접속인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'swgeProcZoneInnerAr', fieldName: 'swgeProcZoneInnerAr', header: { text: '면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, - { name: 'swgeProcZoneOuterSm', fieldName: 'swgeProcZoneOuterSm', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'townUnprocPopltn', fieldName: 'townUnprocPopltn', header: { text: '미처리인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'townSwttPopltn', fieldName: 'townSwttPopltn', header: { text: '오수처리인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'townWrrtnPopltn', fieldName: 'townWrrtnPopltn', header: { text: '정화조인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'swgeProcZoneOuterAr', fieldName: 'swgeProcZoneOuterAr', header: { text: '면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, - { name: 'pblswrgPopltnSuprt', fieldName: 'pblswrgPopltnSuprt', header: { text: '공공하수처리구역인구보급율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, - { name: 'adtrtPopltnSuprt', fieldName: 'adtrtPopltnSuprt', header: { text: '고도처리인구보급율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, - { name: 'swrgInstlrt', fieldName: 'swrgInstlrt', header: { text: '하수도설치율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, - { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + { name: 'spcCtpnRgnCn', fieldName: 'spcCtpnRgnCn', header: { text: '특별대책지역' } }, + { name: 'wholPpltnCnt', fieldName: 'wholPpltnCnt', header: { text: '총인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'wholArea', fieldName: 'wholArea', header: { text: '총면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'wholSwtrmtPpltnCnt', fieldName: 'wholSwtrmtPpltnCnt', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'wholPblSwtrmtPpltnCnt', fieldName: 'wholPblSwtrmtPpltnCnt', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts1SwtrmtPpltnCnt', fieldName: 'ts1SwtrmtPpltnCnt', header: { text: '1차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts2SwtrmtPpltnCnt', fieldName: 'ts2SwtrmtPpltnCnt', header: { text: '2차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts3SwtrmtPpltnCnt', fieldName: 'ts3SwtrmtPpltnCnt', header: { text: '3차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'wholWwtPrcsPpltnCnt', fieldName: 'wholWwtPrcsPpltnCnt', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts1WwtPrcsPpltnCnt', fieldName: 'ts1WwtPrcsPpltnCnt', header: { text: '1차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts2WwtPrcsPpltnCnt', fieldName: 'ts2WwtPrcsPpltnCnt', header: { text: '2차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts3WwtPrcsPpltnCnt', fieldName: 'ts3WwtPrcsPpltnCnt', header: { text: '3차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'yetCntnPpltnCnt', fieldName: 'yetCntnPpltnCnt', header: { text: '미접속인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'staaArea', fieldName: 'staaArea', header: { text: '면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'wholStaaExcpPpltnCnt', fieldName: 'wholStaaExcpPpltnCnt', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'unprcsPpltnCnt', fieldName: 'unprcsPpltnCnt', header: { text: '미처리인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'drtwPrcsPpltnCnt', fieldName: 'drtwPrcsPpltnCnt', header: { text: '오수처리인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'spctnkPpltnCnt', fieldName: 'spctnkPpltnCnt', header: { text: '정화조인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'staaExcpArea', fieldName: 'staaExcpArea', header: { text: '면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'staaPsplyRt', fieldName: 'staaPsplyRt', header: { text: '공공하수처리구역인구보급율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'altdPrcsPsplyRt', fieldName: 'altdPrcsPsplyRt', header: { text: '고도처리인구보급율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'sewerInstlRt', fieldName: 'sewerInstlRt', header: { text: '하수도설치율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, ], columnLayout: [ { @@ -131,56 +111,65 @@ export const SeweragePenetrationRateInfoList = { hideChildHeaders: false, header: { text: '행정구역' }, items: [ - { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, - 'gugun', - 'adstDong', + { column: 'ctpvNm', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'sggNm', + 'admdstNm', ] }, 'displayYear', - 'rvbsn', - 'bstrm', + 'wtsysNm', + 'trbtrNm', { name: 'groupB', hideChildHeaders: false, header: { text: '수계영향권별분류' }, items: ['mdbsn', 'lgbsn', 'mdmbsn'], }, - 'speclCntrplnAr', - 'popltnSm', - 'arSm', + 'spcCtpnRgnCn', + 'wholPpltnCnt', + 'wholArea', { name: 'groupD', hideChildHeaders: false, header: { text: '하수처리구역내' }, items: [ - 'swgeProcZoneInnerSm', + 'wholSwtrmtPpltnCnt', { name: 'pblswrgGroup', hideChildHeaders: false, header: { text: '공공하수처리인구(명)' }, - items: ['pblswrgPopltnSm', 'pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt'], + items: ['wholPblSwtrmtPpltnCnt', 'ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt'], }, { name: 'wstrGroup', hideChildHeaders: false, header: { text: '폐수처리인구(명)' }, - items: ['wstrPopltnSm', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], + items: ['wholWwtPrcsPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt'], }, - 'notConectPopltn', - 'swgeProcZoneInnerAr' + 'yetCntnPpltnCnt', + 'staaArea' ], }, { name: 'groupE', hideChildHeaders: false, header: { text: '하수처리구역외' }, - items: ['swgeProcZoneOuterSm', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn', 'swgeProcZoneOuterAr'], + items: ['wholStaaExcpPpltnCnt', 'unprcsPpltnCnt', 'drtwPrcsPpltnCnt', 'spctnkPpltnCnt', 'staaExcpArea'], }, - 'pblswrgPopltnSuprt', - 'adtrtPopltnSuprt', - 'swrgInstlrt', - 'rmk', + 'staaPsplyRt', + 'altdPrcsPsplyRt', + 'sewerInstlRt', + 'rmrk', ], - fixedColumns: 2, + fixedColumns: 2, rowStartNum: 3 +}; + +export const SeweragePenetrationRateInfoList = { + ...rawSeweragePenetrationRateInfoList, + columns: rawSeweragePenetrationRateInfoList.columns.map(col => ({ + ...col, + editable: false, + styleName: "rg-cell-readonly" + })) }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts b/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts index a5408cc7f..9f280338c 100644 --- a/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts +++ b/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts @@ -115,6 +115,7 @@ export const StatsInputStatusInfoList = { ...StatsInputStatusInfoColumnRaw, columns: StatsInputStatusInfoColumnRaw.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: "rg-cell-readonly" })) }; \ No newline at end of file diff --git a/apps/st/src/components/util/grid/footerSummary.ts b/apps/st/src/components/util/grid/footerSummary.ts index 0b9bd148b..530d65f8c 100644 --- a/apps/st/src/components/util/grid/footerSummary.ts +++ b/apps/st/src/components/util/grid/footerSummary.ts @@ -181,8 +181,8 @@ const createSumFooterArrayByDivision = (format: string, divisionFieldName: strin grid: GridView, column: DataColumn, footerIndex: number, - columnFooter: unknown, - value: number | string | undefined + // columnFooter: unknown, + // value: number | string | undefined ) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName), }, { @@ -191,8 +191,8 @@ const createSumFooterArrayByDivision = (format: string, divisionFieldName: strin grid: GridView, column: DataColumn, footerIndex: number, - columnFooter: unknown, - value: number | string | undefined + // columnFooter: unknown, + // value: number | string | undefined ) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName), }, ]; @@ -205,8 +205,8 @@ const createAvgFooterArrayByDivision = (format: string, divisionFieldName: strin grid: GridView, column: DataColumn, footerIndex: number, - columnFooter: unknown, - value: number | string | undefined + // columnFooter: unknown, + // value: number | string | undefined ) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName), }, { @@ -215,8 +215,8 @@ const createAvgFooterArrayByDivision = (format: string, divisionFieldName: strin grid: GridView, column: DataColumn, footerIndex: number, - columnFooter: unknown, - value: number | string | undefined + // columnFooter: unknown, + // value: number | string | undefined ) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName), }, ]; diff --git a/apps/st/src/entities/statistics/api/analysis-api.ts b/apps/st/src/entities/statistics/api/analysis-api.ts new file mode 100644 index 000000000..0b607b299 --- /dev/null +++ b/apps/st/src/entities/statistics/api/analysis-api.ts @@ -0,0 +1,41 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +import type { + AnalysisRequestPayload, + AnalysisResponseDto, + JobStatusDto, + AnalysisResultDto, +} from "../model/analysis-interface"; + +export class AnalysisApiService { + public async analyze( + payload: AnalysisRequestPayload, + ): Promise { + const response = await client.post( + `${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/analyze`, + payload, + ); + + return response.data; + } + + public async getJobStatus(jobId: string): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/status/${jobId}`, + ); + + return response.data; + } + + public async getResult(jobId: string): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/result/${jobId}`, + ); + + return response.data; + } +} + +export const analysisApi = new AnalysisApiService(); + diff --git a/apps/st/src/entities/statistics/api/analysis-history-api.ts b/apps/st/src/entities/statistics/api/analysis-history-api.ts new file mode 100644 index 000000000..56375bf5f --- /dev/null +++ b/apps/st/src/entities/statistics/api/analysis-history-api.ts @@ -0,0 +1,36 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +import type { + StAnlsMstListApiResponse, + StAnlsMstSearchParams, + StAnlsOutlierListResponse, +} from "../model/analysis-history-interface"; + +export class AnalysisHistoryApiService { + public async getAnalysisHistoryList( + params: StAnlsMstSearchParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}`, + { + params, + }, + ); + + return response.data; + } + + public async getOutlierList( + anlsSn: number, + ): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/outlierList/${anlsSn}`, + ); + + return response.data; + } +} + +export const analysisHistoryApi = new AnalysisHistoryApiService(); + diff --git a/apps/st/src/entities/statistics/api/statistics-analysis-detail-api.ts b/apps/st/src/entities/statistics/api/statistics-analysis-detail-api.ts new file mode 100644 index 000000000..4e7a6e476 --- /dev/null +++ b/apps/st/src/entities/statistics/api/statistics-analysis-detail-api.ts @@ -0,0 +1,36 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +import type { + StAnlsDtlListApiResponse, + StAnlsDtlResponse, + StAnlsDtlSearchParams, +} from "../model/statistics-analysis-detail-interface"; + +export class StAnlsDtlApiService { + public async getDetailList( + params: StAnlsDtlSearchParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}`, + { + params, + }, + ); + + return response.data; + } + + public async getDetail( + anlsSn: number, + vrfcTrgtSn: number, + ): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/${anlsSn}/${vrfcTrgtSn}`, + ); + + return response.data; + } +} + +export const stAnlsDtlApi = new StAnlsDtlApiService(); diff --git a/apps/st/src/entities/statistics/api/statistics-analysis-result-api.ts b/apps/st/src/entities/statistics/api/statistics-analysis-result-api.ts new file mode 100644 index 000000000..604e4144a --- /dev/null +++ b/apps/st/src/entities/statistics/api/statistics-analysis-result-api.ts @@ -0,0 +1,37 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +import type { + ChartRetDto, + StAnlsRsltListApiResponse, + StAnlsRsltSearchParams, +} from "../model/statistics-analysis-result-interface"; + +export class StAnlsRsltApiService { + public async getResultList( + params: StAnlsRsltSearchParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT}`, + { + params, + }, + ); + + return response.data; + } + + public async getChartData( + anlsSn: number, + vrfcTrgtSn: number, + ): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT}/chartList/${anlsSn}/${vrfcTrgtSn}`, + ); + + return response.data; + } +} + +export const stAnlsRsltApi = new StAnlsRsltApiService(); + diff --git a/apps/st/src/entities/statistics/api/statistics-input-service.ts b/apps/st/src/entities/statistics/api/statistics-input-service.ts deleted file mode 100644 index f8fdc0850..000000000 --- a/apps/st/src/entities/statistics/api/statistics-input-service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import axios from "axios"; -import { ENDPOINT } from "@/shared/config"; - -import { - type StatisticsInputService, - type SewerageSupplyRateRequestParams, - type SewerageSupplyRateApiResponse, - type CheckInputPeriodResponse, - type SewerageSupplyRateSaveRequest, - type SewerageSupplyRateSaveApiResponse, -} from "../model/statistics-input-interface"; - -export class StatisticsInputAPiService implements StatisticsInputService { - public async getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise { - const response = await axios.get ( - `${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`, - { - params: params - } - ); - - return response.data; - } - - public async checkInputPeriod(params: string): Promise { - const response = await axios.get ( - `${ENDPOINT.ST_SERVICE.INPUTCOMMON}/check-input-period?year=${params}`, - ); - - return response.data; - } - - public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): 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-input-sew-supply-rate-service.ts b/apps/st/src/entities/statistics/api/statistics-input-sew-supply-rate-service.ts new file mode 100644 index 000000000..ba6d10874 --- /dev/null +++ b/apps/st/src/entities/statistics/api/statistics-input-sew-supply-rate-service.ts @@ -0,0 +1,46 @@ +import { ENDPOINT } from "@/shared/config"; +import { stClient } from "@/shared/api/client"; + +import { + type StatisticsInputSewSupplyRateService, + type SewerageSupplyRateRequestParams, + type SewerageSupplyRateApiResponse, + type CheckInputPeriodResponse, + type SewerageSupplyRateSaveRequest, + type SewerageSupplyRateSaveApiResponse, +} from "../model/statistics-sewage-supply-rate-input-interface"; + +export class StatisticsInputSewSupplyRateApiService implements StatisticsInputSewSupplyRateService { + public async getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`, + { params } + ); + return response.data; + } + + public async checkInputPeriod(params: string): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.INPUTCOMMON}/check-input-period?year=${params}`, + ); + return response.data; + } + + public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise { + const response = await stClient.post( + `${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`, + params, + ); + return response.data; + } + + public async submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise { + const response = await stClient.post( + `${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate/submit`, + params, + ); + return response.data; + } +} + +export const StatisticsInputSewSupplyRateApi = new StatisticsInputSewSupplyRateApiService(); \ No newline at end of file diff --git a/apps/st/src/entities/statistics/api/statistics-input-status-service.ts b/apps/st/src/entities/statistics/api/statistics-input-status-service.ts new file mode 100644 index 000000000..cd3c55a5f --- /dev/null +++ b/apps/st/src/entities/statistics/api/statistics-input-status-service.ts @@ -0,0 +1,20 @@ +import { ENDPOINT } from "@/shared/config"; +import { stClient } from "@/shared/api/client"; + +import type { + StatisticsInputStatusService, + StatsInputStatusListRequestParams, + StatsInputStatusListApiResponse, +} from "../model/statistics-input-status-interface"; + +export class StatisticsInputStatusApiService implements StatisticsInputStatusService { + public async getStatisticsInputStatusList(param: StatsInputStatusListRequestParams): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.INPUT}/stats-input-status`, + { params: param }, + ); + return response.data; + } +} + +export const StatisticsInputStatusApi = new StatisticsInputStatusApiService(); \ 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 89718aaf0..1e2a6336d 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -1,20 +1,34 @@ -import axios from "axios"; import { ENDPOINT } from "@/shared/config"; +import { stClient } from "@/shared/api/client"; -import { - type StatsInputStatusReviewApiResponse -} from "../model/statistics-input-review-interface"; -import { - type SewagePipeReviewRegionalApiResponse -} from "../model/statistics-sewage-pipe-review-interface"; -import { +import { + type StatsInputStatusReviewApiResponse +} from "../model/statistics-input-review-interface"; +import { type SewerageSupplyRateReviewRequestParams, type SewerageSupplyRateReviewApiResponse, -} from "../model/statistics-sewerage-penetration-review-interface"; -import { - type SewagePipeFacilityApiResponse +} from "../model/statistics-sewerage-penetration-review-interface"; +import { + type SewagePipeReviewRequestParams, + type SewagePipeReviewRegionalApiResponse, + type SewagePipeFacilityApiResponse, + type SewerPipeTypeApiResponse, + type SewerPipeDiamApiResponse, } from "../model/statistics-sewage-pipe-review-interface"; +import { + type SewerPipeMtcApiResponse, + type SewerPipeDrdgApiResponse, + type SewerPipeMtcRequestParams +} from "../model/statistics-sewage-pipe-mtc-review-interface"; +import { + type SewerReservoirRegionalApiResponse, + type PumpingStationRegionalApiResponse +} from "../model/sewage-reservoir-pump-review-interface"; +import { + type MediationPumpStationApiResponse, +} from "../model/statistics-pumping-station-review-interface"; + export class StatisticsManagementApiService { /** @@ -24,25 +38,13 @@ export class StatisticsManagementApiService { public async getStatsInputStatusList( params: { yr?: string; bsnmCd?: string; sido?: string; gugun?: string } ): Promise { - const response = await axios.get( + const response = await stClient.get( `${ENDPOINT.ST_SERVICE.REVIEW}/input-status`, { params } ); return response.data; } - /** - * 입력 기간 확인 (공통) - * @param year 연도 - */ - public async checkInputPeriod(year: string | number): Promise { - const response = await axios.get( - `${ENDPOINT.ST_SERVICE.INPUTCOMMON}/check-input-period`, - { params: { year } } - ); - return response.data; - } - /** * 01. 하수도 보급률 검토 목록 조회 * @param params (sido, gugun, yr, bsnmCd) @@ -50,7 +52,7 @@ export class StatisticsManagementApiService { public async getSewerageSupplyRateReviewList( params: SewerageSupplyRateReviewRequestParams ): Promise { - const response = await axios.get( + const response = await stClient.get( `${ENDPOINT.ST_SERVICE.REVIEW}/supply-rate`, { params } ); @@ -62,33 +64,153 @@ export class StatisticsManagementApiService { * @param params (sido, gugun, yr, bsnmCd) */ public async getSewagePipeReviewRegionalList( - params: { - yr?: string; - bsnmCd?: string; - sido?: string; - gugun?: string; - } + params: SewagePipeReviewRequestParams ): Promise { - const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/regional-status`, + const response = await stClient.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: SewagePipeReviewRequestParams + ): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/facility-status`, + { params } + ); + return response.data; + } + + /** + * 02. 하수관로 관종별 시설현황 조회 (Tab3) + * @param params (sido, gugun, yr, bsnmCd) + */ + public async getSewagePipeTypeList( params: { yr?: string; bsnmCd?: string; sido?: string; gugun?: string; } - ): Promise { - const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/facility-status`, + ): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/type-status`, + { params } + ); + return response.data; + } + + /** + * 02. 하수관로 관경별 시설현황 조회 (Tab 4) + * @param params (sido, gugun, yr, bsnmCd) + */ + public async getSewagePipeDiamList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/diam-status`, + { params } + ); + return response.data; + } + + /** + * 03. 하수관로 유지관리 개보수 현황 조회 + * @param params (yr, bsnmCd, sido, gugun) + */ + public async getSewerPipeMtcList( + params: SewerPipeMtcRequestParams + ): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe-mtc/repair`, + { params } + ); + return response.data; + } + + /** + * 03. 하수관로 유지관리 관로준설 현황 조회 (Tab B) + * @param params (yr, bsnmCd, sido, gugun) + */ + public async getSewerPipeDrdgList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe-mtc/dredging`, + { params } + ); + return response.data; + } + + /** + * 04. 하수저류시설 tab1. 시설현황 조회 + * @param params (yr, bsnmCd, sido, gugun) + */ + public async getSewerReservoirList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-reservoir/facilities`, + { params } + ); + return response.data; + } + + /** + * 04. 빗물펌프장 tab2. 조회 + * @param params (yr, bsnmCd, sido, gugun) + */ + public async getPumpStationList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-reservoir/pumping-stations`, + { params } + ); + return response.data; + } + + /** + * 05. 중계펌프장 통계관리 조회 + * @param params (yr, bsnmCd, sido, gugun, bscFctNm) + */ + public async getMediationPumpStationList( + params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업소 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + bscFctNm?: string; // 펌프장명 (시설명) + } + ): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/mediation-pump-stations`, { params } ); return response.data; diff --git a/apps/st/src/entities/statistics/api/statistics-service.ts b/apps/st/src/entities/statistics/api/statistics-service.ts index 8455e68c5..f75163e5c 100644 --- a/apps/st/src/entities/statistics/api/statistics-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-service.ts @@ -1,33 +1,26 @@ -import axios from "axios"; import { ENDPOINT } from "@/shared/config"; +import { stClient } from "@/shared/api/client"; import { type StatisticsService, type SearchCondition, type ApiResponse, - type Sgg + type Sgg, } from "../model/statistics-interface"; export class StatisticsAPiService implements StatisticsService { - public async getSidoList(): Promise> { - const response = await axios.get>( + const response = await stClient.get>( `${ENDPOINT.ST_SERVICE.COMMON}/search-condition`, ); - return response.data; - }; + } public async getSggList(sidoName: string): Promise> { - const response = await axios.get>( + const response = await stClient.get>( `${ENDPOINT.ST_SERVICE.COMMON}/sgg`, - { - params: { - sidoName: sidoName, - }, - } + { params: { sidoName } }, ); - return response.data; } } diff --git a/apps/st/src/entities/statistics/api/verification-target-management-service.ts b/apps/st/src/entities/statistics/api/verification-target-management-service.ts index 5bbcd714c..f7430c28b 100644 --- a/apps/st/src/entities/statistics/api/verification-target-management-service.ts +++ b/apps/st/src/entities/statistics/api/verification-target-management-service.ts @@ -2,6 +2,7 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; import type { + VerificationTargetCodeResponse, VerificationTargetManagementService, VerificationTargetManagementSearchParams, VerificationTargetManagementListApiResponse, @@ -43,6 +44,18 @@ export class VerificationTargetManagementApiService implements VerificationTarge return response.data; } + /** + * 검증 대상 코드 목록 조회 + */ + public async getVerificationTargetCodes(): Promise< + VerificationTargetCodeResponse[] + > { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG}/code`, + ); + return response.data; + } + /** * 검증 대상 등록 * @param request 등록 요청 데이터 diff --git a/apps/st/src/entities/statistics/model/analysis-history-interface.ts b/apps/st/src/entities/statistics/model/analysis-history-interface.ts new file mode 100644 index 000000000..7e5b95ba8 --- /dev/null +++ b/apps/st/src/entities/statistics/model/analysis-history-interface.ts @@ -0,0 +1,51 @@ +import type { PageableList } from "@/shared/model"; + +export interface StAnlsMstResponse { + anlsSn: number; + anlsBgngDt: string; + anlsEndDt: string | null; + anlsSttsCd: string; + anlsSttsNm: string; + anlsPrgrsRt?: number; + nmlDataNocs: number; + exclDataNocs: number; + otlrDataNocs: number; + jobUuid: string; + regDt: string; + rgtr: string; + rgtrNm: string; +} + +export interface StAnlsMstSearchParams { + anlsSttsCd?: string; + jobUuid?: string; + page?: number; + size?: number; + sort?: string; + verifyDtStart?: string; + verifyDtEnd?: string; +} + +export interface StAnlsOutlierDataResponse { + rsltSn: number; + vrfcTrgtSn: number; + admdstCd: string; + sido: string; + gugun: string; + adstDong: string; + addr: string; + cryrVl: number; + otlrTrsfRt: number; +} + +export interface StAnlsOutlierListResponse { + anlsSn: number; + vrfcTrgtSn: number; + vrfcTrgtNm: string; + nmlDataNocs: number; + exclDataNocs: number; + otlrDataNocs: number; + outlierDataList: StAnlsOutlierDataResponse[]; +} + +export type StAnlsMstListApiResponse = PageableList; diff --git a/apps/st/src/entities/statistics/model/analysis-interface.ts b/apps/st/src/entities/statistics/model/analysis-interface.ts new file mode 100644 index 000000000..96ef8a6cc --- /dev/null +++ b/apps/st/src/entities/statistics/model/analysis-interface.ts @@ -0,0 +1,64 @@ +import type { PageableList } from "@/shared/model"; + +export interface AnalysisRequestPayload { + dataset?: string; + yearOld: number; + yearNew: number; + mu: number; + a: number; + c: number; + percentile: number; + sido?: string; + sigungu?: string; + vrfcMthdCd?: string; + allVerifyYn?: "Y" | "N"; +} + +export interface AnalysisResponseDto { + jobId: string; + status: string; + message: string; + queuePosition: number; + createdAt: string; + anlsSn: number; +} + +export interface JobStatusDto { + jobId: string; + status: string; + message: string; + queuePosition: number; + progress: number; + startedAt: string | null; + completedAt: string | null; + error: string | null; + anlsSn: number | null; + processingTimeMs: number | null; +} + +export interface AnalysisSummaryDto { + // 요약 필드는 Python 분석 서비스 스키마에 따라 확장 + [key: string]: unknown; +} + +export interface OutlierRecordDto { + // 상세 구조는 Python 분석 서비스 응답에 맞춰 필요 시 확장 + [key: string]: unknown; +} + +export interface AnalysisResultDto { + jobId: string; + status: string; + createdAt: string; + completedAt: string | null; + summary: AnalysisSummaryDto | null; + multiSummaries: Record | null; + outliers: OutlierRecordDto[]; + results: OutlierRecordDto[]; + totalCount: number; + cached: boolean; + processingTimeMs: number; + anlsSn: number; +} + +export type AnalysisHistoryListApiResponse = PageableList; diff --git a/apps/st/src/entities/statistics/model/sewage-reservoir-pump-review-interface.ts b/apps/st/src/entities/statistics/model/sewage-reservoir-pump-review-interface.ts new file mode 100644 index 000000000..4f7b6bf7f --- /dev/null +++ b/apps/st/src/entities/statistics/model/sewage-reservoir-pump-review-interface.ts @@ -0,0 +1,171 @@ + +import type { ApiResponse } from "./statistics-interface"; + +/** + * 04. 하수저류시설 tab1. 시설현황 아이템 인터페이스 (Tab1) + */ +export interface SewerReservoirRegionalItem { +// 1. 기본 정보 및 행 제어 + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + swgeStrgeNm: string; // 시설명 + addr: string; // 위치(주소) + yr: string; // 연도 + rowType: 'OLD' | 'CURR'; // 전년도/당해년도 구분 + displayYear: string; // 구분 표시용 (2024년, 2023년확정 등) + + // 2. 시설 제원 + rnDt: string; // 가동개시일 (yyyyMMdd) + plDsgnFrqnc: string; // 계획설계빈도 + insPer1: string; // 설치목적: 침수예방 + insPer2: string; // 설치목적: 수질오염저감 + insPer3: string; // 설치목적: 하수재이용 + facRes: string; // 시설구조 + dgrAr: number; // 집수구역면적 + swgeSgbsCpcty: number; // 시설용량 + totCpcty: number; // 총양수량 + + // 3. 펌프시설 상세 (1호기) + pumpCalbr: string; // 구경 + wpCpcty: number; // 대당 양수량 + pumpCo: number; // 펌프대수 + pumpPreparCo: number; // 예비펌프대수 + + // 4. 펌프시설 상세 (2호기) - 추가 + pumpCalbr2: string; // 구경2 + wpCpcty2: number; // 대당 양수량2 + pumpCo2: number; // 펌프대수2 + pumpPreparCo2: number; // 예비펌프대수2 + + // 5. 펌프시설 상세 (3호기) - 추가 + pumpCalbr3: string; // 구경3 + wpCpcty3: number; // 대당 양수량3 + pumpCo3: number; // 펌프대수3 + pumpPreparCo3: number; // 예비펌프대수3 + + // 6. 부대시설 및 방식 - 추가 + freFnnc: string; // 전처리시설유무 + dorzeqpFnnc: string; // 탈취설비유무 + selfEtrqFnnc: string; // 자가발전유무 + draMth: string; // 배수방식 + elecMth: string; // 저류방식 + elecProMat: string; // 저류수 처리방법 + disWat: string; // 방류수역 + + // 7. 최종처분량 및 방법 - 추가 + sdmtTreCpcty: number; // 침사량 + iprtTreCpcty: number; // 협잡물량 + sdmtTreMth: string; // 침사 처분방법 + iprtTreMth: string; // 협잡물 처분방법 + + // 8. 운영 실적 + yrstpRnDyn: number; // 가동일수 + dsplcmntAmnt: number; // 총배수량 + rmk: string; // 비고 + + // 9. 상태 체크용 (그리드 배경색 제어용) - 추가 + lcnt: number; // 신규 여부 (1/0) + ocnt: number; // 기존 여부 (1/0) +} + +/** + * 하수저류시설 응답 데이터 구조 + */ +export interface SewerReservoirResponseData { + reservoirList: SewerReservoirRegionalItem[]; +} + +/** + * 하수저류시설 서비스 인터페이스 + */ +export interface SewerReservoirService { + /** + * 하수저류시설 리스트 조회 (Tab1) + */ + getSewerReservoirList(params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업장 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + }): Promise; +} + +/** + * 04. 빗물펌프장 tab2. 시설현황 아이템 인터페이스 (Tab2) + */ +export interface PumpingStationRegionalItem { + // --- 기본 정보 --- + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + swgeStrgeNm: string; // 시설명 + addr: string; // 위치 + yr: string; // 연도 + rowType: 'OLD' | 'CURR'; // 전년도/당해년도 구분 + displayYear: string; // 구분 표시용 + + // --- 시설 제원 --- + fctKnd: string; // 시설물종류 + rnDt: string; // 가동개시일 + plDsgnFrqnc: string; // 계획설계빈도 + dgrAr: number; // 집수구역면적 + revFnnc: string; // 유수지유무 + revCpcty: number; // 유수지저류량 + absCpcty: number; // 흡수정저류량 + swgeSgbsCpcty: number; // 시설용량 + totCpcty: number; // 총양수량 + + // --- 펌프시설 상세 (1, 2, 3호기) --- + pumpCalbr: string; wpCpcty: number; pumpCo: number; pumpPreparCo: number; + pumpCalbr2: string; wpCpcty2: number; pumpCo2: number; pumpPreparCo2: number; + pumpCalbr3: string; wpCpcty3: number; pumpCo3: number; pumpPreparCo3: number; + + // --- 부대시설 및 방식 --- + freFnnc: string; // 전처리시설유무 + dorzeqpFnnc: string; // 탈취설비유무 + selfEtrqFnnc: string; // 자가발전유무 + disWat: string; // 방류수역 + + // --- 최종처분량 및 방법 --- + sdmtTreCpcty: number; // 침사 처분량 + iprtTreCpcty: number; // 협잡물 처분량 + sdmtTreMth: string; // 침사 처분방법 + iprtTreMth: string; // 협잡물 처분방법 + + // --- 운영실적 --- + yrstpRnDyn: number; // 가동일수 + dsplcmntAmnt: number; // 총배수량 + rmk: string; // 비고 +} + +/** + * 응답 타입 정의 + */ +export type SewerReservoirRegionalApiResponse = ApiResponse; +export type PumpingStationRegionalApiResponse = ApiResponse; + +/** + * 하수저류시설 및 빗물펌프장 서비스 인터페이스 + */ +export interface SewerReservoirService { + /** + * 하수저류시설 리스트 조회 (Tab1) + */ + getSewerReservoirList(params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + }): Promise; + + /** + * 빗물펌프장 리스트 조회 (Tab2) + */ + getPumpStationList(params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + }): Promise; +} \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/statistics-analysis-detail-interface.ts b/apps/st/src/entities/statistics/model/statistics-analysis-detail-interface.ts new file mode 100644 index 000000000..3f6eb9003 --- /dev/null +++ b/apps/st/src/entities/statistics/model/statistics-analysis-detail-interface.ts @@ -0,0 +1,28 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface StAnlsDtlResponse { + anlsSn: number; // 분석 일련번호 + vrfcTrgtSn: number; // 검증 대상 일련번호 + ipcrCntVl: number | null; // 중요도 개수 값 (mu) + pctlCrtrVl: number | null; // 백분위 기준 값 (percentile) + scopAjmtCntVl: number | null; // 범위 조정 개수 값 (A) + thrsCntVl: number | null; // 임계 개수 값 (C) + rtCnterVl: number | null; // 비율 중앙값 + lastTrsfScrCnterVl: number | null; // 최종 변환 점수 중앙값 + lastTrsfScrLlmtVl: number | null; // 최종 변환 점수 하한값 + lastTrsfScrUplmtVl: number | null; // 최종 변환 점수 상한값 + uplmtVl: number | null; // 상한값 + llmtVl: number | null; // 하한값 + otlrUplmtLine: number | null; // 이상치 상한선 + otlrLlmtLine: number | null; // 이상치 하한선 + nmlDataNocs: number; // 정상 데이터 건수 + exclDataNocs: number; // 제외 데이터 건수 + otlrDataNocs: number; // 이상치 데이터 건수 +} + +export interface StAnlsDtlSearchParams extends PageableParams { + anlsSn?: number; // 분석 일련번호 + vrfcTrgtSn?: number; // 검증 대상 일련번호 +} + +export type StAnlsDtlListApiResponse = PageableList; diff --git a/apps/st/src/entities/statistics/model/statistics-analysis-result-interface.ts b/apps/st/src/entities/statistics/model/statistics-analysis-result-interface.ts new file mode 100644 index 000000000..38c44b7a9 --- /dev/null +++ b/apps/st/src/entities/statistics/model/statistics-analysis-result-interface.ts @@ -0,0 +1,46 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +import type { StAnlsDtlResponse } from "./statistics-analysis-detail-interface"; + +export interface StAnlsRsltResponse { + rsltSn: number; // 결과 일련번호 + anlsSn: number; // 분석 일련번호 + vrfcTrgtSn: number; // 검증 대상 일련번호 + admdstCd: string; // 행정구역 코드 + admdstNm: string; // 행정구역 명칭 + sido: string; // 시도 + gugun: string; // 시군구 + adstDong: string; // 행정동 + drngBzmnCd: string; // 하수도 사업자 코드 + pipeSeCd: string; // 파이프 구분 코드 + fcltNm: string; // 시설명 + addr: string; // 주소 + prvyrVl: number | null; // 전년도 값 + cryrVl: number | null; // 당해년도 값 + chgRt: number | null; // 변경율 (r_i) + trsfRt: number | null; // 변환 비율 (s_i) + otlrTrsfRt: number | null; // 이상치 변환 비율 (E_i) + otlrYn: string; // 이상치 여부 (y/n) +} + +export interface StAnlsRsltSearchParams extends PageableParams { + anlsSn?: number; // 분석 일련번호 + vrfcTrgtSn?: number; // 검증 대상 일련번호 + admdstCd?: string; // 행정구역 코드 + admdstNm?: string; // 행정구역 명칭 + fcltNm?: string; // 시설명 + otlrYn?: string; // 이상치 여부 (y/n) +} + +export type StAnlsRsltListApiResponse = PageableList; + +export interface ChartBoundaryPointDto { + xPrev: number; // 전년도 값 (x축) + yLower: number; // 이상치 하한 당년값 (y축) + yUpper: number; // 이상치 상한 당년값 (y축) +} + +export interface ChartRetDto { + dtl: StAnlsDtlResponse; // 분석 상세 파라미터 (mu, r_mid, T_L, T_U 등) + list: ChartBoundaryPointDto[]; // HBR 경계선 곡선 데이터 리스트 (x_prev, y_lower, y_upper) +} diff --git a/apps/st/src/entities/statistics/model/statistics-input-interface.ts b/apps/st/src/entities/statistics/model/statistics-input-interface.ts deleted file mode 100644 index 3fd0ba0c8..000000000 --- a/apps/st/src/entities/statistics/model/statistics-input-interface.ts +++ /dev/null @@ -1,198 +0,0 @@ -import type { ApiResponse } from "./statistics-interface"; - -// 하수도 보급률 데이터 항목 -export interface SewerageSupplyRateStatusInfo { - locgStatus: string; - locgDt: string; - locgCharger: string; - locgCttpc: string; - prvnStatus: string; - dcsnStatus: string; - wrterOpinionSm: string | null; - chckerOpinionSm: string | null; - 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; - adstDong: string; - rgnNm: string; - adstCd: string; - yr: string; - bsnmCd: string; - rvbsn: string; - lgbsn: string; - mdbsn: string; - mdmbsn: string; - totalPopl: number; - poplSm: number; - frgnrPoplSm: number; - area: number; - bsnmTotalPopl: number; - bsnmPoplSm: number; - bsnmFrgnrPoplSm: number; - bsnmArea: number; - piplPlanExtn: number; - piplFcltyExtn: number; - piplSupprt: number; - wrssm: string | null; - bstrm: string; - speclCntrplnAr: string | null; - popltnSm: number; - arSm: number; - swgeProcZoneInnerSm: number; - pblswrgPopltnSm: number; - pblswrgPrmTrt: number; - pblswrgScdTrt: number; - pblswrgTertTrt: number; - wstrPopltnSm: number; - wstrPrmTrt: number; - wstrScdTrt: number; - wstrTertTrt: number; - swgeProcZoneInnerAr: number; - swgeProcZoneOuterSm: number; - townUnprocPopltn: number; - townSwttPopltn: number; - townWrrtnPopltn: number; - uptownUnprPopltn: number; - uptownSwttPopltn: number; - uptownWrrtnPopltn: number; - swgeProcZoneOuterAr: number; - pblswrgPopltnSuprt: number; - adtrtPopltnSuprt: number; - swrgInstlrt: number; - wrterOpinion: string | null; - chckerOpinion: string | null; - notConectPopltn: number; - rmk: string; - chkWrssm: string; - chkBstrm: string; - chkSpeclCntrplnAr: string; - chkPopltnSm: string; - chkArSm: string; - chkSwgeProcZoneInnerSm: string; - chkPblswrgPopltnSm: string; - chkPblswrgPrmTrt: string; - chkPblswrgScdTrt: string; - chkPblswrgTertTrt: string; - chkWstrPopltnSm: string; - chkWstrPrmTrt: string; - chkWstrScdTrt: string; - chkWstrTertTrt: string; - chkSwgeProcZoneInnerAr: string; - chkSwgeProcZoneOuterSm: string; - chkTownUnprocPopltn: string; - chkTownSwttPopltn: string; - chkTownWrrtnPopltn: string; - chkUptownUnprPopltn: string; - chkUptownSwttPopltn: string; - chkUptownWrrtnPopltn: string; - chkSwgeProcZoneOuterAr: string; - chkPblswrgPopltnSuprt: string; - chkAdtrtPopltnSuprt: string; - chkSwrgInstlrt: string; - chkNotConectPopltn: string; - verificationResult?: SewerageSupplyRateVerificationResult; -} - -export interface SewerageSupplyRateResponseData { - statusInfo: SewerageSupplyRateStatusInfo; - sewerageSupplyRateList: SewerageSupplyRateItem[]; - commonInfo: SewerageSupplyRateCommonInfo; -} - -export type SewerageSupplyRateApiResponse = ApiResponse & { - message: string; -} - -export interface SewerageSupplyRateRequestParams { - sido?: string; - gugun?: string; - bsnmCd?: string; - yr?: string; - mode?: string; -} - -/** 하수도 보급률 저장 API 요청 body (한 행) */ -export interface SewerageSupplyRateSaveItem { - gugun: string | null; - 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; -} - -export interface SewerageSupplyRateSaveRequest { - yr: string; - sido: string; - bsnmCd: string; - userId: string; - saveList: SewerageSupplyRateSaveItem[]; -} - -export interface SewerageSupplyRateSaveData { - hasMismatch?: boolean; - mismatchIdList?: string[]; - httpStatus?: number; - code?: string; -} - -export type SewerageSupplyRateSaveApiResponse = ApiResponse; - -// 하수도 통계 입력 기간 조회 -export interface CheckInputPeriodResponse { - status: string; - message: string; - data: boolean; -} - -export interface StatisticsInputService { - getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise; - checkInputPeriod(params: string): Promise; - saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise; -} \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/statistics-input-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-input-review-interface.ts index 93f1a4651..ff86a8799 100644 --- a/apps/st/src/entities/statistics/model/statistics-input-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-input-review-interface.ts @@ -104,7 +104,7 @@ export type StatsInputStatusReviewApiResponse = ApiResponse & { + message: string; +} + +export interface StatisticsInputStatusService { + getStatisticsInputStatusList(param: StatsInputStatusListRequestParams): Promise; +} diff --git a/apps/st/src/entities/statistics/model/statistics-input-submit-info-schema.ts b/apps/st/src/entities/statistics/model/statistics-input-submit-info-schema.ts new file mode 100644 index 000000000..623d037df --- /dev/null +++ b/apps/st/src/entities/statistics/model/statistics-input-submit-info-schema.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; + +export const statisticsInputSubmitInfoSchema = z.object({ + // 작성자 명, 작성자 직제, 작성자 부서, 작성자 연락처, 승인자명, 승인자 직제, 승인자 부서, 승인자 연락처 (전부 필수) + laePicNm: z.string().min(1, "작성자 명을 입력하세요"), + laeKbrdrJbgdNm: z.string().min(1, "작성자 직제를 입력하세요"), + laeKbrdrDeptNm: z.string().min(1, "작성자 부서를 입력하세요"), + laePicTelNo: z.string().min(1, "작성자 연락처를 입력하세요"), + laeIdfrNm: z.string().min(1, "승인자명을 입력하세요"), + laeIdfrJbgdNm: z.string().min(1, "승인자 직제를 입력하세요"), + laeIdfrDeptNm: z.string().min(1, "승인자 부서를 입력하세요"), + laeIdfrTelNo: z.string().min(1, "승인자 연락처를 입력하세요"), +}); + +export type StatisticsInputSubmitInfoFormData = z.infer< + typeof statisticsInputSubmitInfoSchema +>; \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts new file mode 100644 index 000000000..fbfc04711 --- /dev/null +++ b/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts @@ -0,0 +1,86 @@ +import type { ApiResponse } from "./statistics-interface"; + +/** + * 중계펌프장 통계관리 아이템 인터페이스 + */ +export interface MediationPumpStationItem { + // 기본 정보 및 행 제어 + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + pumpNm: string; // 펌프장명 + yr: string; // 연도 + displayYear: string; // 구분 표시용 (2024년확정 등) + rowType: 'OLD' | 'CURR'; // 전년도/당해년도 구분 + + // 주요 제원 + bscFctNm: string; // 처리장명 + pumpCls: string; // 구분 (합류식/분류식) + addr: string; // 위치 + rnDt: string; // 가동년월일 + planDrhgAr: number; // 계획배수면적(㎢) + finewtDrngVl: number; // 계획시간대최대배수량 - 청천시(A) + rainwtDrngVl: number; // 계획시간대최대배수량 - 우천시(B) + vlWtr: number; // 계획시간대최대배수량 - 우수량(B-A) + + // 오수펌프 상세 (PLTWTR) + pltwtrTotCpcty: number; // 오수 총양수량(㎥/min) + pltwtrPumpCalbr1: number; // 오수펌프1 구경(mm) + pltwtrPumpCpcty1: number; // 오수펌프1 양수량(㎥/min) + pltwtrPumpCnt1: number; // 오수펌프1 펌프대수 + pltwtrPumpPreparCnt1: number; // 오수펌프1 예비대수 + + pltwtrPumpCalbr2: number; // 오수펌프2 구경(mm) + pltwtrPumpCpcty2: number; // 오수펌프2 양수량(㎥/min) + pltwtrPumpCnt2: number; // 오수펌프2 펌프대수 + pltwtrPumpPreparCnt2: number; // 오수펌프2 예비대수 + + pltwtrPumpCalbr3: number; // 오수펌프3 구경(mm) + pltwtrPumpCpcty3: number; // 오수펌프3 양수량(㎥/min) + pltwtrPumpCnt3: number; // 오수펌프3 펌프대수 + pltwtrPumpPreparCnt3: number; // 오수펌프3 예비대수 + + // 우수펌프 상세 (RAINWTR) + rainwtrTotCpcty: number; // 우수 총양수량(㎥/min) + rainwtrPumpCalbr1: number; // 우수펌프1 구경(mm) + rainwtrPumpCpcty1: number; // 우수펌프1 양수량(㎥/min) + rainwtrPumpCnt1: number; // 우수펌프1 펌프대수 + rainwtrPumpPreparCnt1: number; // 우수펌프1 예비대수 + + rainwtrPumpCalbr2: number; // 우수펌프2 구경(mm) + rainwtrPumpCpcty2: number; // 우수펌프2 양수량(㎥/min) + rainwtrPumpCnt2: number; // 우수펌프2 펌프대수 + rainwtrPumpPreparCnt2: number; // 우수펌프2 예비대수 + + rainwtrPumpCalbr3: number; // 우수펌프3 구경(mm) + rainwtrPumpCpcty3: number; // 우수펌프3 양수량(㎥/min) + rainwtrPumpCnt3: number; // 우수펌프3 펌프대수 + rainwtrPumpPreparCnt3: number; // 우수펌프3 예비대수 + + // 기타 + elcpwAmnt: number; // 연간전력사용량(kWh/년) + rmk: string; // 비고 + lcnt: string; // 당해 존재 여부 ('1'|'0') + ocnt: string; // 전년 존재 여부 ('1'|'0') +} + + +export interface MediationPumpStationResponseData { + pumpStationList: MediationPumpStationItem[]; +} + + +export type MediationPumpStationApiResponse = ApiResponse & { + message: string; +}; + + +export interface MediationPumpStationService { + getMediationPumpStationList(params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업소 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + bscFctNm?: string; // 펌프장명 + }): Promise; +} \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts new file mode 100644 index 000000000..0281e43bc --- /dev/null +++ b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts @@ -0,0 +1,103 @@ +import type { ApiResponse } from "./statistics-interface"; + +/** + * 03. 하수관로 유지관리 개보수 아이템 + */ + +export interface SewerPipeMtcItem { + ctpvNm: string; + sggNm: string; + statsYr: string; + rowType: 'OLD' | 'CURR'; + displayYear: string; + sewerBzmnCd: string; + admdstNm:string; + mendngWhol: number; + cbsysMendngPrlg: number; + sprsysDrtwPrlg: number; + sprsysExlnPrlg: number; + partMendngWhol: number; + cbsysPartMendngPrlg: number; + sprsysDrtwMendngPrlg: number; + sprsysExlnMendngPrlg: number; + cbsysMnhlCnt: number; + sprsysDrtwMnhlCnt: number; + sprsysExlnMnhlCnt: number; + exlnRcvprpCnt: number; + drtwRcvprpCnt: number; + stofcbrCnt: number; + otflCnt: number; + cctvExmnPrlg: number; + rmrk: string | null; +} + +export interface SewerPipeMtcResponseData { + mtcStatusList: SewerPipeMtcItem[]; +} + +export interface SewerPipeMtcRequestParams { + ctpvNm?: string; + sggNm?: string; + statsYr?: string; + sewerBzmnCd?: string; +} + +export type SewerPipeMtcApiResponse = ApiResponse; + +export interface SewagePipeMtcService { + getSewerPipeMtcYearlyStatus(params: SewerPipeMtcRequestParams): Promise; +} + + +/** + * 03. 하수관로 유지관리 관로준설 아이템 (Tab 2) + */ +export interface SewerPipeDrdgItem { + // 1. 기본 정보 + sido: string; // 시도 + gugun: string; // 시군구 + yr: string; // 연도 + displayYear: string; // 화면 표시용 (예: 2024년, 2023년확정) + rowType: 'OLD' | 'CURR'; // OLD / CURR 구분 + bsnmCd: string; // 사업소코드 + + // 2. 시설 현황 + piplFcltyExtn: number; // 관로연장 (m) + + // 3. 준설 실적 - 관로 (m) + drdgPipeSm: number; // 준설계 + drdgCmbnPipe: number; // 합류식 + drdgPltwtrPipe: number; // 분류식-오수 + drdgRainwtrPipe: number; // 분류식-우수 + + // 4. 준설 실적 - 부속시설 (개소) + mnhleSite: number; // 맨홀 + pltwtrRcvrSite: number; // 오수받이 + soilspllSoilholeSite: number; // 토실/토구 + + // 5. 준설 효율 및 성과 + drdgTrgetExtn: number; // 준설대상 연장 (m) + drdgTrgetRt: number; // 준설대상 대비 준설률 (%) + piplFcltyRt: number; // 시설연장 대비 준설률 (%) + drdgAcmsltQy: number; // 준설실적 - 양 (m3) + drdgAcmsltAmount: number; // 준설실적 - 금액 (천원) + drdgAcmsltUntpc: number; // 준설실적 - 단가 (원/m3) + + // 6. 준설토 처리 (톤) + drdgEtndSm: number; // 처리량 계 + rclm: number; // 매립 + oceanDpsl: number; // 해양투기 + ruse: number; // 재활용 + etc: number; // 기타 + + // 7. 기타 및 상태값 + rmk: string; // 비고 +} + + +export interface SewerPipeDrdgResponseData { + drdgStatusList: SewerPipeDrdgItem[]; +} + + +export type SewerPipeDrdgApiResponse = ApiResponse; \ 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 0d82096ad..3d97ec558 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 @@ -4,131 +4,132 @@ import type { ApiResponse } from "./statistics-interface"; * 02. 하수관로 tab1.지역별 시설현황 아이템 인터페이스 (Tab1) */ export interface SewagePipeReviewrRegionalItem { - // 기본 정보 및 행 제어 - sido: string; // 시도 - gugun: string; // 시군구 - adstDong: string; // 행정구역명 - adstCd: string; // 행정코드 - yr: string; // 연도 - rowType: 'OLD' | 'CURR'; // 전년도/당해년도 구분 - displayYear: string; // 구분 표시용 (2024년확정 등) - // 하수정비 기본계획 - targetYr: number; // 하수정보기본계획목표년도 - piplImprmnPlanExtn: number; // 총관로정비 계획연장(m) - piplPlanExtn: number; // 총계획연장(m) - piplFcltyExtn: number; // 총시설연장(m) - piplSupprt: number; // 하수관로 보급률(%) +/* --- 기본 정보 및 행 제어 --- */ + ctpvNm: string; // 시도명 + sggNm: string; // 시군구명 + admdstNm: string; // 행정구역명 + admdstCd: string; // 행정구역코드 + statsYr: string; // 통계 연도 + rowType: 'OLD' | 'CURR'; // 데이터 구분 (OLD: 확정분, CURR: 당해년도) + displayYear: string; // 화면 표시용 연도 - // 합류식 (CMBN) - cmbnPlanAr: number; // 관로정비 계획면적(㎢) - cmbnImprmnPlanExtn: number; // 관로정비 계획연장(m) - cmbnPlanExtn: number; // 계획연장(m) - cmbnFcltyExtn: number; // 시설연장(m) - cmbnRctglExtn: number; // 사각형(m) - cmbnCicleExtn: number; // 원형(m) - cmbnOpswrExtn: number; // 개거(m) - cmbnSdgttrExtn: number; // 측구(m) + /* --- 하수정비 기본계획 --- */ + targetYr: number; // 하수정보기본계획 목표년도 + swrgPipeMntnPlanExtn: number; // 총 관로정비 계획연장(m) + swrgPipePlanExtn: number; // 총 계획연장(m) + swrgPipeFcltyExtn: number; // 총 시설연장(m) + swrgPipePsplyRt: number; // 하수관로 보급률(%) - // 분류식 - 오수관로 (PLTWTR) - pltwtrPlanAr: number; // 관로정비 계획면적(㎢) - pltwtrImprmnPlanExtn: number; // 관로정비 계획연장(m) - pltwtrPlanExtn: number; // 계획연장(m) - pltwtrFlctyExtn: number; // 시설연장(m) - pltwtrSqreExtn: number; // 사각형(m) - pltwtrCiclExtn: number; // 원형(m) + /* --- 합류식 (Combined System) --- */ + cmbnMntnPlanAr: number; // 관로정비 계획면적(㎢) + cmbnMntnPlanExtn: number; // 관로정비 계획연장(m) + cmbnPlanExtn: number; // 계획연장(m) + cmbnFcltyExtn: number; // 시설연장(m) + cmbnRctglExtn: number; // 사각형 암거 연장(m) + cmbnCicleExtn: number; // 원형 암거 연장(m) + cmbnOpswrExtn: number; // 개거 연장(m) + cmbnSdgttrExtn: number; // 측구 연장(m) - // 분류식 - 우수관로 (RAINWTR) - rainwtrPlanAr: number; // 관로정비 계획면적(㎢) - rainwtrImprmnPlanExtn: number; // 관로정비 계획연장(m) - rainwtrPlanExtn: number; // 계획연장(m) - rainwtrFlctyExtn: number; // 시설연장(m) - rainwtrSqreExtn: number; // 사각형(m) - rainwtrCiclExtn: number; // 원형(m) - rainwtrOpswrExtn: number; // 개거(m) - rainwtrSdgttrExtn: number; // 측구(m) + /* --- 분류식 - 오수관로 (Foul Water Pipe) --- */ + pltwtrMntnPlanAr: number; // 관로정비 계획면적(㎢) + pltwtrMntnPlanExtn: number; // 관로정비 계획연장(m) + pltwtrPlanExtn: number; // 계획연장(m) + pltwtrFcltyExtn: number; // 시설연장(m) + pltwtrRctglExtn: number; // 사각형 암거 연장(m) + pltwtrCicleExtn: number; // 원형 암거 연장(m) - // 부속시설 (개소) - mnhleSite: number; // 합류식맨홀 - pltwtrMnhleSite: number; // 오수맨홀 - rainwtrMnhleSite: number; // 우수맨홀 - pltwtrRcvrSite: number; // 오수받이 - rainwtrRcvrSite: number; // 빗물받이 - soilspllSite: number; // 우수토실 - soilholeSite: number; // 우수토구 + /* --- 분류식 - 우수관로 (Rain Water Pipe) --- */ + rainwtrMntnPlanAr: number; // 관로정비 계획면적(㎢) + rainwtrMntnPlanExtn: number; // 관로정비 계획연장(m) + rainwtrPlanExtn: number; // 계획연장(m) + rainwtrFcltyExtn: number; // 시설연장(m) + rainwtrRctglExtn: number; // 사각형 암거 연장(m) + rainwtrCicleExtn: number; // 원형 암거 연장(m) + rainwtrOpswrExtn: number; // 개거 연장(m) + rainwtrSdgttrExtn: number; // 측구 연장(m) + + /* --- 부속시설 (개소) --- */ + cmbnMnhleCnt: number; // 합류식 맨홀 수 + pltwtrMnhleCnt: number; // 오수 맨홀 수 + rainwtrMnhleCnt: number; // 우수 맨홀 수 + pltwtrRcvrCnt: number; // 오수받이 수 + rainwtrRcvrCnt: number; // 우수받이(빗물받이) 수 + rainwtrChmbrCnt: number; // 우수토실 수 + rainwtrOutltCnt: number; // 우수토구 수 + + /* --- 대장 및 기타 --- */ + swrgRegstrCmptExtn: number; // 하수도대장 전산화 연장(m) + swrgRegstrCmptRt: number; // 하수도대장 전산화율(%) + rmrk: string; // 비고 - // 대장 및 기타 - sewerRegstrCmptExtn: number; // 하수도대장 전산화연장(m) - sewerRegstrCmptRt: number; // 하수도대장 전산화율(%) - rmk: string; // 비고 } export interface SewagePipeReviewResponseData { sewagePipeList: SewagePipeReviewrRegionalItem[]; } +export interface SewagePipeReviewRequestParams { + ctpvNm?: string; // 시도명 + sggNm?: string; // 시군구명 + statsYr?: string; // 통계 연도 + sewerBzmnCd?: string; // 하수도사업자 코드 +} export type SewagePipeReviewRegionalApiResponse = ApiResponse & { message: string; }; export interface SewagePipeReviewService { - - getSewagePipeReviewList(params: { - yr?: string; // 조회 연도 - bsnmCd?: string; // 사업장 코드 - sido?: string; // 시도 - gugun?: string; // 구군 - }): Promise; + getSewagePipeReviewRegionalList(params: SewagePipeReviewRequestParams): Promise; } - +/** + * 02. 하수관로 시설별 설치현황 데이터 항목 (Tab 2) + */ export interface SewagePipeReviewFacilityItem { // 1. 행정구역 및 기본 정보 - sido: string; // 시도 - gugun: string; // 시군구 - adstCd: string; // 행정코드 - adstDong: string; // 행정구역명 - yr: string; // 연도 - rowType: 'OLD' | 'CURR'; // 전년도/당해년도 구분 - displayYear: string; // 구분 표시용 (2024년확정 등) + ctpvNm: string; // 시도명 + sggNm: string; // 시군구명 + admdstCd: string; // 행정구역코드 + admdstNm: string; // 행정구역명 + statsYr: string; // 통계연도 + rowType: 'OLD' | 'CURR'; // 데이터 구분 (OLD: 확정, CURR: 당해년도) + displayYear: string; // 화면 표시용 연도 // 2. 시설 정보 - bscFctNm: string; // 하수처리장명 - fctCpc: number; // 하수처리시설용량(㎥/일) + swrmtFcltyNm: string; // 하수처리시설 명칭 + swrmtFcltyCpc: number; // 하수처리시설 용량(㎥/일) - // 3. 처리구역면적(㎢) - procZoneSm: number; // 합계(면적) - cmbnProcZoneAr: number; // 합류식(면적) - sprtProcZoneAr: number; // 분류식(면적) - sepRate: number; // 분류식화 (%) + // 3. 처리구역 면적(㎢) + procZoneArSm: number; // 처리구역 면적 합계 + cmbnProcZoneAr: number; // 합류식 처리구역 면적 + sprsysProcZoneAr: number; // 분류식 처리구역 면적 // 4. 배수설비(개소) - drngEqpSm: number; // 합계(배수) - cmbnDrngEqp: number; // 합류식(배수) - sprtDrngEqpSm: number; // 합계(분류배수) - sprtDrngEqpCnnc: number; // 연결 - sprtDrngEqpDcnnc: number; // 미연결 + drngEqpSm: number; // 배수설비 합계 + cmbnDrngEqp: number; // 합류식 배수설비 + sprsysDrngEqpSm: number; // 분류식 배수설비 합계 // 5. 하수관로 (m) - piplFcltyExtn: number; // 총 시설연장(m) - icgMnnSm: number; // 합계(차집/오수간선) - icgExtn: number; // 차집 - mnnExtn: number; // 오수간선 + swrgPipeFcltyExtn: number; // 총 시설연장 + icgPltwtrMnnSm: number; // 차집 및 오수간선 합계 + icgExtn: number; // 차집관로 연장 + pltwtrMnnExtn: number; // 오수간선 연장 - // 6. 토실/토구 (개소) - cmbnSoilholeSite: number; // 우수토실(합류) - sprtSoilholeSite: number; // 우수토실(분류) - sprtSoilspllSite: number; // 우수토구(분류) + // 6. 우수토실/토구 (개소) + cmbnRainwtrChmbrCnt: number; // 합류식 우수토실 + sprsysRainwtrChmbrCnt: number; // 분류식 우수토실 + sprsysRainwtrOutltCnt: number; // 분류식 우수토구 // 7. 지선 (m) - brnchlnSm: number; // 합계(지선) - cmbnBrnchlnExtn: number; // 합류식(지선) - rainwtrBrnchlnExtn: number; // 우수(지선) - pltwtrBrnchlnExtn: number; // 오수(지선) + brnchlnSm: number; // 지선 합계 + cmbnBrnchlnExtn: number; // 합류식 지선연장 + sprsysExlnBrnchlnExtn: number; // 분류식 우수지선연장 + sprsysDrtwBrnchlnExtn: number; // 분류식 오수지선연장 // 비고 - rmk: string; // 비고 + rmrk: string | null; // 비고 } @@ -136,17 +137,98 @@ export interface SewagePipeFacilityResponseData { facilityPipeList: SewagePipeReviewFacilityItem[]; } -export type SewagePipeFacilityApiResponse = ApiResponse & { + +export type SewagePipeFacilityApiResponse = ApiResponse & { message: string; }; export interface SewagePipeFacilityService { + getSewagePipeFacilityList(params: SewagePipeReviewRequestParams): Promise; +} - getSewagePipeFacilityList(params: { +/** + * 02. 하수관로 관종별 시설현황 (Tab 3) + * + */ +export interface SewerPipeTypeItem { + // --- SewerPipeTab3Response 기본 필드 --- + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + swrgPipeNm: string; // 관종명 + displayYear: string; // 구분 표시용 (예: 2024년확정) + yr: string; // 연도 + rmk: string; // 비고 + yrGbn?: string; // 구분 (매설/철거 등 행 구분용 - 그리드 설정 기준) + + // --- BasePipeExtnResponse 상속 필드 --- + layingExtn: number; // 매설연장(m) - 당해년도 + + // 총계(Sum) 필드 (그리드 설정에서 사용하는 합계용 필드) + totalExtnSum?: number; + layingExtnSum?: number; + remvlExtnSum?: number; + + // 01y ~ 30y 동적 필드 처리 (Index Signature) + // 예: totalExtn01y, layingExtn05y, remvlExtn30y 등 + [key: `totalExtn${string}y`]: number | undefined; + [key: `layingExtn${string}y`]: number | undefined; + [key: `remvlExtn${string}y`]: number | undefined; +} + + +export interface SewerPipeTypeResponseData { + pipeTypeStatusList: SewerPipeTypeItem[]; +} + +export type SewerPipeTypeApiResponse = ApiResponse; + +export interface SewagePipeTypeService { + + getSewagePipeTypeList(params: { yr?: string; // 조회 연도 bsnmCd?: string; // 사업장 코드 sido?: string; // 시도 gugun?: string; // 구군 - }): Promise; + }): Promise; +} + +/** + * 02. 하수관로 관경별 시설현황 아이템 (Tab 4) + */ +export interface SewerPipeDiamItem { + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + swrgPipeNm: string; // 관종명 (또는 관경 관련 명칭) + displayYear: string; // 구분 (예: 2023년 확정 / 2024년) + + // 관경별 수치 데이터 + under200: number; // 200미만 + over200Undev300: number; // 200~300 + over300Undev500: number; // 300~500 + over500Undev500: number; // 500~800 (DTO 명칭 기준) + over800: number; // 800이상 + box: number; // BOX + etc: number; // 기타 + rmk: string; // 비고 +} + + +export interface SewerPipeDiamResponseData { + pipeDiamStatusList: SewerPipeDiamItem[]; +} + + +export type SewerPipeDiamApiResponse = ApiResponse; + + +export interface SewagePipeDiamService { + getSewagePipeDiamList(params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업장 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + }): Promise; } \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/statistics-sewage-supply-rate-input-interface.ts b/apps/st/src/entities/statistics/model/statistics-sewage-supply-rate-input-interface.ts new file mode 100644 index 000000000..5c1a1f6ef --- /dev/null +++ b/apps/st/src/entities/statistics/model/statistics-sewage-supply-rate-input-interface.ts @@ -0,0 +1,170 @@ +import type { ApiResponse } from "./statistics-interface"; + +// 하수도 보급률 데이터 항목 +export interface SewerageSupplyRateStatusInfo { + laeSbmsnSttsCd: string | null; + laeSbmsnYmd: string | null; + laePicNm: string | null; + laePicTelNo: string | null; + laeKbrdrJbgdNm: string | null; + prvnStatus: string | null; + dcsnStatus: string | null; + wrterOpinionSm: string | null; + chckerOpinionSm: string | null; + laeIdfrDeptNm: string | null; + laeIdfrJbgdNm: string | null; + laeIdfrNm: string | null; + laeIdfrTelNo: string | null; + laeKbrdrDeptNm: string | null; +} + +export interface SewerageSupplyRateCommonInfo { + sewerBzmnArea: number; + sewerBzmnCd: string; + sewerBzmnFrgnrWholPpltnCnt: number; + sewerBzmnDomplWholPpltnCnt: number; + sewerBzmnWholPpltnCnt: number; + piplFcltyExtn: number; + piplPlanExtn: number; + swagePplPsplyRt: number; + ctpvNm: string; + statsYr: string; +} + +/** 검증 결과 한 건 (필드별 메시지) */ +export interface VerificationResultItem { + vrfcCd: string; + rsnExpln: string; +} + +/** 필드명 → 검증 결과 배열. 예: { popltnSm: [{ type, rsn }], wstrPrmTrt: [...] } */ +export type SewerageSupplyRateVerificationResult = Record; + +/** 하수도 보급률 목록 API 응답 한 행 (실제 응답 필드만) */ +export interface SewerageSupplyRateItem { + admdstCd: string; + sggNm: string; + admdstNm: string; + rvbsn: string | null; + trbtrNm: string; + mdbsn: string; + lgbsn: string; + mdmbsn: string; + spcCtpnRgnCn: string | null; + totalPopl: number; + poplSm: number; + frgnrPoplSm: number; + refArea: number; + wholPpltnCnt: number; + wholArea: number; + wholSwtrmtPpltnCnt: number; + wholPblSwtrmtPpltnCnt: number; + ts1SwtrmtPpltnCnt: number; + ts2SwtrmtPpltnCnt: number; + ts3SwtrmtPpltnCnt: number; + wholWwtPrcsPpltnCnt: number; + ts1WwtPrcsPpltnCnt: number; + ts2WwtPrcsPpltnCnt: number; + ts3WwtPrcsPpltnCnt: number; + yetCntnPpltnCnt: number; + staaArea: number; + wholStaaExcpPpltnCnt: number; + unprcsPpltnCnt: number; + drtwPrcsPpltnCnt: number; + spctnkPpltnCnt: number; + staaExcpArea: number; + staaPsplyRt: number; + altdPrcsPsplyRt: number; + sewerInstlRt: number; + rmrk: string | null; + verificationResult?: SewerageSupplyRateVerificationResult | null; +} + +export interface SewerageSupplyRateResponseData { + statusInfo: SewerageSupplyRateStatusInfo; + sewerageSupplyRateList: SewerageSupplyRateItem[]; + commonInfo: SewerageSupplyRateCommonInfo; +} + +export type SewerageSupplyRateApiResponse = ApiResponse & { + message: string; +} + +export interface SewerageSupplyRateRequestParams { + ctpvNm?: string; + sggNm?: string; + sewerBzmnCd?: string; + statsYr?: string; + mode?: string; +} + +/** 하수도 보급률 저장 API 요청 body (한 행) */ +export interface SewerageSupplyRateSaveItem { + sggNm: string | null; + admdstCd: string; + admdstNm: string; + trbtrNm: string; + spcCtpnRgnCn: string | null; + ts1SwtrmtPpltnCnt: number; + ts2SwtrmtPpltnCnt: number; + ts3SwtrmtPpltnCnt: number; + ts1WwtPrcsPpltnCnt: number; + ts2WwtPrcsPpltnCnt: number; + ts3WwtPrcsPpltnCnt: number; + yetCntnPpltnCnt: number; + staaArea: number; + unprcsPpltnCnt: number; + drtwPrcsPpltnCnt: number; + spctnkPpltnCnt: number; + staaExcpArea: number; + rmrk: string; + wholPpltnCnt: number; + wholArea: number; + wholSwtrmtPpltnCnt: number; + wholPblSwtrmtPpltnCnt: number; + wholWwtPrcsPpltnCnt: number; + wholStaaExcpPpltnCnt: number; + staaPsplyRt: number; + altdPrcsPsplyRt: number; + sewerInstlRt: number; +} + +export interface SewerageSupplyRateSaveRequest { + statsYr: string; + ctpvNm: string; + sewerBzmnCd: string; + userId: string; + sewerBzmnFrgnrWholPpltnCnt?: number; + saveList?: SewerageSupplyRateSaveItem[]; + submitList?: SewerageSupplyRateSaveItem[]; + laePicNm?: string; + laeKbrdrJbgdNm?: string; + laeKbrdrDeptNm?: string; + laePicTelNo?: string; + laeIdfrNm?: string; + laeIdfrJbgdNm?: string; + laeIdfrDeptNm?: string; + laeIdfrTelNo?: string; + superAdmin?: string; +} + +export interface SewerageSupplyRateSaveData { + httpStatus?: number; + code?: string; +} + +export type SewerageSupplyRateSaveApiResponse = ApiResponse; + +// 하수도 통계 입력 기간 조회 +export interface CheckInputPeriodResponse { + status: string; + message: string; + data: boolean; +} + +export interface StatisticsInputSewSupplyRateService { + getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise; + checkInputPeriod(params: string): Promise; + saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise; + submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise; +} \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/statistics-sewerage-penetration-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-sewerage-penetration-review-interface.ts index 8fbf7ea1f..9c4d0ac1e 100644 --- a/apps/st/src/entities/statistics/model/statistics-sewerage-penetration-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-sewerage-penetration-review-interface.ts @@ -2,69 +2,55 @@ import type { ApiResponse } from "./statistics-interface"; // 검토 01.하수도 보급률 데이터 항목 export interface SewerageSupplyRateReviewItem { +ctpvNm: string; + sggNm: string; + statsYr: string; + admdstNm: string; + admdstCd: string; + displayYear: string; + rowType: 'OLD' | 'CURR'; - sido: string; - gugun: string; - adstDong: string; - displayYear: string; - rowType: 'OLD' | 'CURR'; - adstCd: string; - bsnmCd: string; - yr: number; + lgbsn: string; + mdbsn: string; + mdmbsn: string; + rvbsn: string | null; + trbtrNm: string | null; + spcCtpnRgnCn: string | null; - /* ====================================================================== - * 수계 및 지역 정보 - * ====================================================================== */ - rvbsn: string; - lgbsn: string; - mdbsn: string; - mdmbsn: string; - wrssm: string | null; - bstrm: string; - speclCntrplnAr: string | null; + wholPpltnCnt: number; + wholArea: number; - /* ====================================================================== - * 인구 및 면적 데이터 (공통 필드명) - * ====================================================================== */ - popltnSm: number; // 총인구 - arSm: number; // 총면적(㎢) - - /* 하수처리구역내 (groupD) */ - swgeProcZoneInnerSm: number; // 계 - pblswrgPopltnSm: number; // 공공하수처리인구-합계 - pblswrgPrmTrt: number; // 1차처리 - pblswrgScdTrt: number; // 2차처리 - pblswrgTertTrt: number; // 3차처리 + wholSwtrmtPpltnCnt: number; + wholPblSwtrmtPpltnCnt: number; + ts1SwtrmtPpltnCnt: number; + ts2SwtrmtPpltnCnt: number; + ts3SwtrmtPpltnCnt: number; - wstrPopltnSm: number; // 폐수처리인구-합계 - wstrPrmTrt: number; // 1차처리 - wstrScdTrt: number; // 2차처리 - wstrTertTrt: number; // 3차처리 + wholWwtPrcsPpltnCnt: number; + ts1WwtPrcsPpltnCnt: number | null; + ts2WwtPrcsPpltnCnt: number | null; + ts3WwtPrcsPpltnCnt: number | null; - notConectPopltn: number; // 미접속인구 - swgeProcZoneInnerAr: number; // 면적(㎢) + yetCntnPpltnCnt: number; + staaArea: number; - /* 하수처리구역외 (groupE) */ - swgeProcZoneOuterSm: number; // 계 - townUnprocPopltn: number; // 미처리인구 (Town + Uptown 합산값) - townSwttPopltn: number; // 오수처리인구 (Town + Uptown 합산값) - townWrrtnPopltn: number; // 정화조인구 (Town + Uptown 합산값) - swgeProcZoneOuterAr: number; // 면적(㎢) + wholStaaExcpPpltnCnt: number; + unprcsPpltnCnt: number | null; + drtwPrcsPpltnCnt: number | null; + spctnkPpltnCnt: number | null; + staaExcpArea: number; - /* ====================================================================== - * 보급률 및 기타 - * ====================================================================== */ - pblswrgPopltnSuprt: number; // 공공하수보급률(%) - adtrtPopltnSuprt: number; // 고도처리보급률(%) - swrgInstlrt: number; // 하수도설치율(%) - rmk: string | null; // 비고 - - /* ====================================================================== - * Flag (필요 시 유지) - * ====================================================================== */ - lcnt: number | null; - ocnt: number | null; + staaPsplyRt: number; + altdPrcsPsplyRt: number; + sewerInstlRt: number; + rmrk: string | null; +} +export interface SewerageSupplyRateReviewRequestParams { + ctpvNm?: string; + sggNm?: string; + statsYr?: string; + sewerBzmnCd?: string; } export interface SewerageSupplyRateReviewResponseData { @@ -75,14 +61,6 @@ export type SewerageSupplyRateReviewApiResponse = ApiResponse; } \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/verification-history-column.ts b/apps/st/src/entities/statistics/model/verification-history-column.ts deleted file mode 100644 index aea9b7f4f..000000000 --- a/apps/st/src/entities/statistics/model/verification-history-column.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { ValueType } from "realgrid"; -import type { ColumnInfo } from "@rsone/components"; - -/** 검증 이력 그리드 컬럼 설정 */ -export const verificationHistoryColumnInfo: ColumnInfo = { - columns: [ - { - name: "executedAt", - fieldName: "executedAt", - header: { text: "검증 실행 일시" }, - editable: false, - sortdisable: false, - }, - { - name: "progressStatus", - fieldName: "progressStatus", - header: { text: "진행 상태" }, - editable: false, - sortdisable: false, - }, - { - name: "endedAt", - fieldName: "endedAt", - header: { text: "종료 일시" }, - editable: false, - sortdisable: false, - }, - { - name: "verificationYear", - fieldName: "verificationYear", - header: { text: "검증년도" }, - editable: false, - sortdisable: false, - }, - { - name: "mu", - fieldName: "mu", - header: { text: "μ" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0.###", - }, - { - name: "percentile", - fieldName: "percentile", - header: { text: "Percentile" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0.###", - }, - { - name: "a", - fieldName: "a", - header: { text: "A" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0.###", - }, - { - name: "c", - fieldName: "c", - header: { text: "C" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0.###", - }, - { - name: "target", - fieldName: "target", - header: { text: "검증 대상" }, - editable: false, - sortdisable: false, - }, - { - name: "normalCount", - fieldName: "normalCount", - header: { text: "정상건수" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0", - }, - { - name: "excludedCount", - fieldName: "excludedCount", - header: { text: "제외건수" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0", - }, - { - name: "abnormalCount", - fieldName: "abnormalCount", - header: { text: "이상건수" }, - editable: false, - sortdisable: false, - valueType: ValueType.NUMBER, - dataFormat: "#,##0", - }, - ], - columnLayout: [ - "executedAt", - "progressStatus", - "endedAt", - "verificationYear", - "mu", - "percentile", - "a", - "c", - "target", - "normalCount", - "excludedCount", - "abnormalCount", - ], -}; diff --git a/apps/st/src/entities/statistics/model/verification-history-columns.tsx b/apps/st/src/entities/statistics/model/verification-history-columns.tsx new file mode 100644 index 000000000..766709217 --- /dev/null +++ b/apps/st/src/entities/statistics/model/verification-history-columns.tsx @@ -0,0 +1,93 @@ +import { createColumnHelper } from "@tanstack/react-table"; +import { Progress } from "@common/components"; +import type { StAnlsMstResponse } from "./analysis-history-interface"; + +const columnHelper = createColumnHelper(); + +export const verificationHistoryColumns = [ + columnHelper.accessor("anlsBgngDt", { + header: "검증 실행 일시", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor( + (row) => ({ + anlsSttsCd: row.anlsSttsCd, + anlsSttsNm: row.anlsSttsNm, + anlsPrgrsRt: row.anlsPrgrsRt, + }), + { + id: "anlsSttsNm", + header: "진행 상태", + cell: (info) => { + const { anlsSttsCd, anlsSttsNm, anlsPrgrsRt } = info.getValue(); + if (anlsSttsCd === "PROCESSING") { + return ( +
+ + + {anlsPrgrsRt ?? 0}% + +
+ ); + } + return anlsSttsNm; + }, + }, + ), + columnHelper.accessor("anlsEndDt", { + header: "종료 일시", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("rgtrNm", { + header: "검증자", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("nmlDataNocs", { + header: "정상건수", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("exclDataNocs", { + header: "제외건수", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("otlrDataNocs", { + header: "이상건수", + cell: (info) => { + const value = info.getValue() ?? "-"; + const row = info.row.original; + const onOutlierCellClick = ( + info.table.options.meta as { + onOutlierCellClick?: (row: StAnlsMstResponse) => void; + } + )?.onOutlierCellClick; + if (value > 0 && onOutlierCellClick) { + return ( + + ); + } + return value; + }, + }), +]; diff --git a/apps/st/src/entities/statistics/model/verification-history-interface.ts b/apps/st/src/entities/statistics/model/verification-history-interface.ts deleted file mode 100644 index 869399cba..000000000 --- a/apps/st/src/entities/statistics/model/verification-history-interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** 검증 이력 한 건 (그리드 행 데이터) */ -export interface VerificationHistoryItem { - /** 검증 실행 일시 */ - executedAt: string; - /** 진행 상태 */ - progressStatus: string; - /** 종료 일시 */ - endedAt: string; - /** 검증년도 */ - verificationYear: string; - /** μ */ - mu: number; - /** Percentile */ - percentile: number; - /** A */ - a: number; - /** C */ - c: number; - /** 검증 대상 */ - target: string; - /** 정상건수 */ - normalCount: number; - /** 제외건수 */ - excludedCount: number; - /** 이상건수 */ - abnormalCount: number; -} diff --git a/apps/st/src/entities/statistics/model/verification-history-mock.ts b/apps/st/src/entities/statistics/model/verification-history-mock.ts deleted file mode 100644 index 90c152be7..000000000 --- a/apps/st/src/entities/statistics/model/verification-history-mock.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { fakerKO as faker } from "@faker-js/faker"; -import type { VerificationHistoryItem } from "./verification-history-interface"; - -const VERIFICATION_TARGETS = [ - "하수도 보급률", - "하수관로", - "하수관로 유지관리", - "하수저류시설 및 빗물펌프장", - "펌프장", - "공공하수처리시설", - "분뇨처리시설", - "개인하수처리시설", - "하수도재정", - "물 재이용", - "하수도 서비스 수준", -]; - -const PROGRESS_STATUSES = ["대기", "진행중", "완료", "실패"]; - -function formatDateTime(date: Date): string { - const y = date.getFullYear(); - const m = String(date.getMonth() + 1).padStart(2, "0"); - const d = String(date.getDate()).padStart(2, "0"); - const h = String(date.getHours()).padStart(2, "0"); - const min = String(date.getMinutes()).padStart(2, "0"); - return `${y}-${m}-${d} ${h}:${min}`; -} - -/** 검증 이력 목데이터 생성 (그리드 행 형식) */ -export function createVerificationHistoryMock( - count = 10, -): Record[] { - faker.seed(42); - return Array.from({ length: count }, () => { - const executedAt = faker.date.recent({ days: 90 }); - const durationMinutes = faker.number.int({ min: 1, max: 120 }); - const endedAt = new Date( - executedAt.getTime() + durationMinutes * 60 * 1000, - ); - const status = faker.helpers.arrayElement(PROGRESS_STATUSES); - const normalCount = faker.number.int({ min: 0, max: 500 }); - const excludedCount = faker.number.int({ min: 0, max: 50 }); - const abnormalCount = faker.number.int({ min: 0, max: 30 }); - const item: VerificationHistoryItem = { - executedAt: formatDateTime(executedAt), - progressStatus: status, - endedAt: formatDateTime(endedAt), - verificationYear: String(executedAt.getFullYear()), - mu: faker.number.float({ min: 0, max: 1, fractionDigits: 3 }), - percentile: faker.number.float({ - min: 0, - max: 100, - fractionDigits: 2, - }), - a: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }), - c: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }), - target: faker.helpers.arrayElement(VERIFICATION_TARGETS), - normalCount, - excludedCount, - abnormalCount, - }; - return { ...item } as Record; - }); -} - -/** 검증 이력 기본 목데이터 (10건) */ -export const verificationHistoryMockData = createVerificationHistoryMock(10); diff --git a/apps/st/src/entities/statistics/model/verification-target-management-interface.ts b/apps/st/src/entities/statistics/model/verification-target-management-interface.ts index 08d771256..5a7df41fd 100644 --- a/apps/st/src/entities/statistics/model/verification-target-management-interface.ts +++ b/apps/st/src/entities/statistics/model/verification-target-management-interface.ts @@ -1,6 +1,12 @@ import type { PageableList } from "@/shared/model"; import type { ApiResponse } from "./statistics-interface"; +// 검증 대상 코드 응답 DTO +export interface VerificationTargetCodeResponse { + vrfcTrgtSn: string; + vrfcTrgtNm: string; +} + // 검증 대상 관리 응답 DTO export interface VerificationTargetManagementResponse { vrfcTrgtSn: number; // 검증 대상 일련번호 @@ -90,6 +96,8 @@ export interface VerificationTargetManagementService { vrfcTrgtSn: number, ): Promise; + getVerificationTargetCodes(): Promise; + saveVerificationTargetManagement( request: VerificationTargetManagementSaveRequest, ): Promise; diff --git a/apps/st/src/pages/statistics/ui/statistics-page.tsx b/apps/st/src/pages/statistics/ui/statistics-page.tsx index 7513b0ffe..a228a4d46 100644 --- a/apps/st/src/pages/statistics/ui/statistics-page.tsx +++ b/apps/st/src/pages/statistics/ui/statistics-page.tsx @@ -3,6 +3,7 @@ import { Suspense, useRef, useEffect } from "react"; import dynamic from "next/dynamic"; import { useTabStore } from "@/store/tab-store"; +import { useAdminStore } from "@/store/admin-store"; import Dashboard from "@/components/Dashboard"; import { SkeletonPage } from "@/shared/ui/skeleton-page"; @@ -10,6 +11,7 @@ import "@rsone/components/index.css"; const StatisticsPage = () => { const { tabs, activeTabId } = useTabStore(); + const fetchAdminInfo = useAdminStore((state) => state.fetchAdminInfo); // 컴포넌트 캐시를 useRef로 유지 (재렌더링 시에도 유지됨) const componentCache = useRef(new Map()); @@ -26,6 +28,10 @@ const StatisticsPage = () => { }); }, [tabs]); + useEffect(() => { + fetchAdminInfo(); + }, [fetchAdminInfo]); + // 탭이 없으면 Dashboard 표시 if (tabs.length === 0) { return ( diff --git a/apps/st/src/shared/api/client.ts b/apps/st/src/shared/api/client.ts index f4696eb1d..61368d875 100644 --- a/apps/st/src/shared/api/client.ts +++ b/apps/st/src/shared/api/client.ts @@ -1,5 +1,5 @@ import { Client } from "@common/network"; -import { BASE_URL } from "../config/api"; +import { BASE_URL, ST_SERVICE_BASE_URL } from "../config/api"; import { SITE_ID } from "../config/env"; import { setCookie } from "../lib"; @@ -8,4 +8,9 @@ setCookie("site-id-key", SITE_ID); export const client = Client.getInstance({ baseURL: BASE_URL, siteId: SITE_ID, +}); + +export const stClient = Client.getInstance({ + baseURL: ST_SERVICE_BASE_URL, + siteId: SITE_ID, }); \ No newline at end of file diff --git a/apps/st/src/shared/config/api.ts b/apps/st/src/shared/config/api.ts index 1b9cf74ec..8fa5dce05 100644 --- a/apps/st/src/shared/config/api.ts +++ b/apps/st/src/shared/config/api.ts @@ -2,6 +2,8 @@ import { PROXY_HOST } from "./env"; export const BASE_URL = `${PROXY_HOST}/server`; +export const ST_SERVICE_BASE_URL = `${PROXY_HOST}/server`; + export const ENDPOINT = { ATTACH_SERVICE: { TUS_UPLOAD: "/attach-service/api/v1/tus/upload", @@ -72,6 +74,10 @@ export const ENDPOINT = { }, STATVERIFY_SERVICE: { VRFCTGT_MNG: "/statverify-service/api/v1/vrfc-trgt-mng", + ANALYSIS: "/statverify-service/api/v1/stat-verify/analysis", + ST_ANLS_MST: "/statverify-service/api/v1/st-anls-mst", + ST_ANLS_DTL: "/statverify-service/api/v1/st-anls-dtl", + ST_ANLS_RSLT: "/statverify-service/api/v1/st-anls-rslt", }, }; diff --git a/apps/st/src/shared/model/api-interface.ts b/apps/st/src/shared/model/api-interface.ts index 8218bfb0d..17707db3d 100644 --- a/apps/st/src/shared/model/api-interface.ts +++ b/apps/st/src/shared/model/api-interface.ts @@ -1,34 +1,42 @@ interface SortConfig { - empty: boolean; - sorted: boolean; - unsorted: boolean; + empty: boolean; + sorted: boolean; + unsorted: boolean; } interface PageableConfig { - sort: SortConfig; - offset: number; - pageNumber: number; - pageSize: number; - paged: boolean; - unpaged: boolean; + sort: SortConfig; + offset: number; + pageNumber: number; + pageSize: number; + paged: boolean; + unpaged: boolean; } export interface PageableList { - content: T[]; - pageable: PageableConfig; - sort: SortConfig; - totalPages: number; - totalElements: number; - number: number; - numberOfElements: number; - size: number; - first: boolean; - last: boolean; - empty: boolean; + content: T[]; + pageable: PageableConfig; + sort: SortConfig; + totalPages: number; + totalElements: number; + number: number; + numberOfElements: number; + size: number; + first: boolean; + last: boolean; + empty: boolean; } export interface PageableParams { - page?: number; - size?: number; - sort?: string; + page?: number; + size?: number; + sort?: string; +} + +export interface ApiErrorResponse { + timestamp: string; + message: string; + status: number; + code: string; + errors: unknown[]; } diff --git a/apps/st/src/shared/ui/tab-bar.tsx b/apps/st/src/shared/ui/tab-bar.tsx index 3ba4e1977..392e1b199 100644 --- a/apps/st/src/shared/ui/tab-bar.tsx +++ b/apps/st/src/shared/ui/tab-bar.tsx @@ -22,7 +22,7 @@ const TabBar = () => { @@ -30,7 +30,7 @@ const TabBar = () => { diff --git a/apps/st/src/store/admin-store.ts b/apps/st/src/store/admin-store.ts new file mode 100644 index 000000000..1b9937d4f --- /dev/null +++ b/apps/st/src/store/admin-store.ts @@ -0,0 +1,29 @@ +import { create } from "zustand"; +import { UserApi } from "@/entities/user"; +import { getUserInfo } from "../../../../packages/common/components/src/api"; + +interface AdminStore { + /** 현재 사용자가 관리자 목록에 있는지 */ + isAdminInfo: boolean; + setAdminInfo: (value: boolean) => void; + /** API로 관리자 목록 조회 후 현재 사용자 일치 여부 설정 */ + fetchAdminInfo: () => Promise; +} + +const useAdminStoreHook = create((set) => ({ + isAdminInfo: false, + setAdminInfo: (value) => set({ isAdminInfo: value }), + fetchAdminInfo: async () => { + try { + const userInfo = await getUserInfo(); + const adminList = await UserApi.findPageAdminUser({ userName: userInfo.userName }); + const matched = adminList.content?.filter((a) => a.userName === userInfo.userName) ?? []; + set({ isAdminInfo: matched.length > 0 }); + } catch { + set({ isAdminInfo: false }); + } + }, +})); + +export const useAdminStore = useAdminStoreHook; +export const getAdminStoreState = () => useAdminStoreHook.getState(); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..0b4394396 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,38 @@ +import simpleImportSort from "eslint-plugin-simple-import-sort"; +import tsParser from "@typescript-eslint/parser"; + +export default [ + { + files: ["**/*.{js,jsx,ts,tsx}"], + languageOptions: { + parser: tsParser, + }, + plugins: { + "simple-import-sort": simpleImportSort, + }, + rules: { + "simple-import-sort/imports": [ + "warn", + { + groups: [ + ["^react", "^next"], + ["^@?\\w"], + [ + "^@/shared", + "^@/entities", + "^@/features", + "^@/widgets", + "^@/app", + "^@common", + ], + ["^\\u0000"], + ["^\\.\\.(?!/?$)", "^\\.\\./?$"], + ["^\\./(?=.*/)", "^\\.(?!/?$)", "^\\./?$"], + ["^.+\\.?(css|scss)$"], + ], + }, + ], + "simple-import-sort/exports": "warn", + }, + }, +]; diff --git a/package.json b/package.json index f48207433..7c0fdf369 100644 --- a/package.json +++ b/package.json @@ -8,28 +8,28 @@ "apps/*", "packages/*" ], - "scripts": { - "build": "turbo run build", - "build:common": "turbo run build --filter=@common/*", - "dev": "turbo run dev", - "build:cm": "turbo run build --filter=cm", - "build:cs": "turbo run build --filter=cs", - "build:sn": "turbo run build --filter=sn", - "build:wr": "turbo run build --filter=wr", - "build:fo": "turbo run build --filter=fo", - "build:dm": "turbo run build --filter=dm", - "build:dm_new": "turbo run build --filter=dm_new", - "dev:cm": "turbo run dev --filter=cm", - "dev:cs": "turbo run dev --filter=cs", - "dev:sn": "turbo run dev --filter=sn", - "dev:wr": "turbo run dev --filter=wr", - "dev:fo": "turbo run dev --filter=fo", - "dev:dm": "turbo run dev --filter=dm", - "dev:dm_new": "turbo run dev --filter=dm_new", - "preview:components": "turbo run storybook --filter=@common/components", - "preinstall": "npx only-allow pnpm", - "prepare": "husky" - }, +"scripts": { + "build": "turbo run build", + "build:common": "turbo run build --filter=@common/*", + "dev": "turbo run dev", + "build:cm": "turbo run build --filter=cm", + "build:cs": "turbo run build --filter=cs", + "build:sn": "turbo run build --filter=sn", + "build:wr": "turbo run build --filter=wr", + "build:fo": "turbo run build --filter=fo", + "build:dm": "turbo run build --filter=dm", + "build:dm_new": "turbo run build --filter=dm_new", + "dev:cm": "turbo run dev --filter=cm", + "dev:cs": "turbo run dev --filter=cs", + "dev:sn": "turbo run dev --filter=sn", + "dev:wr": "turbo run dev --filter=wr", + "dev:fo": "turbo run dev --filter=fo", + "dev:dm": "turbo run dev --filter=dm", + "dev:dm_new": "turbo run dev --filter=dm_new", + "preview:components": "turbo run storybook --filter=@common/components", + "preinstall": "npx only-allow pnpm", + "prepare": "husky" +}, "dependencies": { "chart.js": "^4.4.8", "react": "^19.2.1", @@ -38,9 +38,12 @@ "zustand": "^5.0.3" }, "devDependencies": { + "@eslint/eslintrc": "^3", "@types/node": "^22.13.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", + "@typescript-eslint/parser": "^8.56.1", + "eslint-plugin-simple-import-sort": "^12.1.1", "husky": "^9.1.7", "lint-staged": "^16.2.7", "turbo": "2.4.2" diff --git a/packages/common/components/src/ui/calendar/index.tsx b/packages/common/components/src/ui/calendar/index.tsx index f2af829e5..1f4bce7e9 100644 --- a/packages/common/components/src/ui/calendar/index.tsx +++ b/packages/common/components/src/ui/calendar/index.tsx @@ -67,7 +67,6 @@ function Calendar({ ), dropdowns: cn( "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-xl font-medium", - captionLayout === "dropdown" && "flex-row-reverse", defaultClassNames.dropdowns, ), dropdown_root: cn( diff --git a/packages/common/components/src/ui/date-range-picker/index.tsx b/packages/common/components/src/ui/date-range-picker/index.tsx index 1e8cdfe4c..58fa324d7 100644 --- a/packages/common/components/src/ui/date-range-picker/index.tsx +++ b/packages/common/components/src/ui/date-range-picker/index.tsx @@ -10,7 +10,10 @@ import { Button } from "../button"; import { Calendar } from "../calendar"; import { Popover, PopoverContent, PopoverTrigger } from "../popover"; -type DateRangePickerProps = React.HTMLAttributes & +type DateRangePickerProps = Omit< + React.HTMLAttributes, + "defaultValue" +> & React.ComponentProps & { value?: DateRange; defaultValue?: DateRange; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a7066496..e39d4b41f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: specifier: ^5.0.3 version: 5.0.9(@types/react@19.2.7)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: + '@eslint/eslintrc': + specifier: ^3 + version: 3.3.3 '@types/node': specifier: ^22.13.1 version: 22.19.2 @@ -41,6 +44,12 @@ importers: '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.7) + '@typescript-eslint/parser': + specifier: ^8.56.1 + version: 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-simple-import-sort: + specifier: ^12.1.1 + version: 12.1.1(eslint@9.39.2(jiti@2.6.1)) husky: specifier: ^9.1.7 version: 9.1.7 @@ -51,7 +60,7 @@ importers: specifier: 2.4.2 version: 2.4.2 - apps/(template): + apps/_template: dependencies: '@common/assets': specifier: workspace:* @@ -92,10 +101,10 @@ importers: devDependencies: eslint: specifier: ^9.0.0 - version: 9.39.2(jiti@1.21.7) + version: 9.39.2(jiti@2.6.1) eslint-config-next: specifier: 15.4.0 - version: 15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + version: 15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) postcss: specifier: ^8.4.49 version: 8.5.6 @@ -1103,6 +1112,9 @@ importers: chart.js: specifier: ^4.4.8 version: 4.5.1 + chartjs-plugin-zoom: + specifier: ^2.2.0 + version: 2.2.0(chart.js@4.5.1) dayjs: specifier: ^1.11.13 version: 1.11.19 @@ -1178,10 +1190,13 @@ importers: version: 5.28.5 eslint: specifier: ^9 - version: 9.39.2(jiti@2.6.1) + version: 9.39.2(jiti@1.21.7) eslint-config-next: specifier: 15.1.7 - version: 15.1.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 15.1.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-hooks: + specifier: ^5.0.0 + version: 5.2.0(eslint@9.39.2(jiti@1.21.7)) postcss: specifier: ^8 version: 8.5.6 @@ -3829,6 +3844,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hammerjs@2.0.46': + resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} + '@types/js-cookie@3.0.6': resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} @@ -3883,22 +3901,45 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.49.0': resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@8.49.0': resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.49.0': resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/type-utils@8.49.0': resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3910,12 +3951,22 @@ packages: resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.49.0': resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.49.0': resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3927,6 +3978,10 @@ packages: resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -4289,6 +4344,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base64-arraybuffer@1.0.2: resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} engines: {node: '>= 0.6.0'} @@ -4311,6 +4370,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -4387,6 +4450,11 @@ packages: resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} engines: {pnpm: '>=8'} + chartjs-plugin-zoom@2.2.0: + resolution: {integrity: sha512-in6kcdiTlP6npIVLMd4zXZ08PDUXC52gZ4FAy5oyjk1zX3gKarXMAof7B9eFiisf9WOC3bh2saHg+J5WtLXZeA==} + peerDependencies: + chart.js: '>=3.2.0' + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -4948,6 +5016,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-simple-import-sort@12.1.1: + resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==} + peerDependencies: + eslint: '>=5.0.0' + eslint-plugin-storybook@0.11.6: resolution: {integrity: sha512-3WodYD6Bs9ACqnB+TP2TuLh774c/nacAjxSKOP9bHJ2c8rf+nrhocxjjeAWNmO9IPkFIzTKlcl0vNXI2yYpVOw==} engines: {node: '>= 18'} @@ -4970,6 +5043,10 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5248,6 +5325,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hammerjs@2.0.8: + resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} + engines: {node: '>=0.8.0'} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -5828,6 +5909,10 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -6947,6 +7032,12 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -9229,6 +9320,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/hammerjs@2.0.46': {} + '@types/js-cookie@3.0.6': {} '@types/json-schema@7.0.15': {} @@ -9356,6 +9449,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.49.0(typescript@5.6.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.6.3) @@ -9374,11 +9479,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.49.0': dependencies: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.6.3)': dependencies: typescript: 5.6.3 @@ -9387,6 +9506,10 @@ snapshots: dependencies: typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.49.0 @@ -9425,6 +9548,8 @@ snapshots: '@typescript-eslint/types@8.49.0': {} + '@typescript-eslint/types@8.56.1': {} + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.6.3)': dependencies: '@typescript-eslint/project-service': 8.49.0(typescript@5.6.3) @@ -9455,6 +9580,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) @@ -9493,6 +9633,11 @@ snapshots: '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -9902,6 +10047,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-arraybuffer@1.0.2: {} baseline-browser-mapping@2.9.7: {} @@ -9921,6 +10068,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -10000,6 +10151,12 @@ snapshots: dependencies: '@kurkle/color': 0.3.4 + chartjs-plugin-zoom@2.2.0(chart.js@4.5.1): + dependencies: + '@types/hammerjs': 2.0.46 + chart.js: 4.5.1 + hammerjs: 2.0.8 + check-error@2.1.1: {} chokidar@3.6.0: @@ -10572,6 +10729,26 @@ snapshots: optionalDependencies: source-map: 0.7.6 + eslint-config-next@15.1.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 15.1.7 + '@rushstack/eslint-patch': 1.15.0 + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7)) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + eslint-config-next@15.1.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 15.1.7 @@ -10592,26 +10769,6 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-config-next@15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): - dependencies: - '@next/eslint-plugin-next': 15.4.0 - '@rushstack/eslint-patch': 1.15.0 - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.2(jiti@1.21.7) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7)) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - eslint-import-resolver-webpack - - eslint-plugin-import-x - - supports-color - eslint-config-next@15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 15.4.0 @@ -10844,6 +11001,10 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.2(jiti@2.6.1)): + dependencies: + eslint: 9.39.2(jiti@2.6.1) + eslint-plugin-storybook@0.11.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.6.3): dependencies: '@storybook/csf': 0.1.13 @@ -10868,6 +11029,8 @@ snapshots: eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} + eslint@9.39.2(jiti@1.21.7): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) @@ -11204,6 +11367,8 @@ snapshots: graceful-fs@4.2.11: {} + hammerjs@2.0.8: {} + has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -11716,6 +11881,10 @@ snapshots: min-indent@1.0.1: {} + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -12892,6 +13061,10 @@ snapshots: dependencies: typescript: 5.9.3 + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-dedent@2.2.0: {} ts-interface-checker@0.1.13: {}