This commit is contained in:
2026-03-09 18:02:44 +09:00
80 changed files with 6225 additions and 2604 deletions
+5 -1
View File
@@ -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]"
+3 -1
View File
@@ -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",
+19 -14
View File
@@ -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*", "/"],
};
+2
View File
@@ -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",
-129
View File
@@ -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;
}
}
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface CancelSubmitButtonProps {
cnacelSubmitFunc: () => void;
}
const CancelSubmitButton = ({ cnacelSubmitFunc }: CancelSubmitButtonProps) => {
return (
<Button className="h-[3.2rem] w-[96px]" color="red" onClick={cnacelSubmitFunc}>
<i className="ri-arrow-reset"></i>
<span>제출 취소</span>
</Button>
);
};
export default CancelSubmitButton;
+46 -1
View File
@@ -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];
}
}
}
@@ -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<RealGridReact>(null);
const rowDataRef = useRef<Record<string, string | number>[]>(rowData);
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(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<string, string | number>;
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<string, string | number>[] = [];
for (let i = 0; i < cnt; i++) {
const r: Record<string, string | number> = {};
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<string, string | number>);
} catch (error) {
console.error("행 데이터 가져오기 오류:", error);
}
};
return () => {
gridView.onCellClicked = undefined;
};
}, [onRowClick]);
return (
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem] w-1/2">
@@ -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<string, string | number>[]): void }
| undefined;
if (dp?.setFields) {
dp.setFields(buildFieldsFromColumns(columns));
}
if (dp?.setRows && rowData?.length) {
dp.setRows(rowData);
}
}}
>
{columns.map((column) => (
+132 -182
View File
@@ -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<string, "asc" | "desc" | "none">,
) {
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<string, string | number>[],
sortField: string,
@@ -415,7 +318,9 @@ const DataGrid = ({
setRowData,
getGridDataRef,
cellStyleCallback,
cellTooltipCallback,
onRowClick,
statusInfo,
}: DataGridProps) => {
const { columns, columnLayout } = columnInfo;
const gridRef = useRef<RealGridReact>(null);
@@ -424,8 +329,10 @@ const DataGrid = ({
const originalRowsRef = useRef<Record<string, Record<string, string | number>[]>>({});
const rowDataRef = useRef<Record<string, string | number>[]>(rowData);
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(cellStyleCallback);
const cellTooltipCallbackRef = useRef<CellTooltipCallback | undefined>(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<string, string | number>, 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<string, string | number>);
} catch (error) {
console.error("행 데이터 가져오기 오류:", error);
}
};
// cleanup: 컴포넌트 언마운트 시 이벤트 핸들러 제거
return () => {
if (gridView.onCellClicked) {
gridView.onCellClicked = undefined;
}
if (gridView.onCellClicked) gridView.onCellClicked = undefined;
};
}, [onRowClick]);
}, [onRowClick, cellTooltipCallback, columns]);
return (
<div className="mx-[0.6rem] flex flex-col gap-[0.6rem]">
@@ -1126,6 +1038,26 @@ const DataGrid = ({
onChange={handleFileChange}
/>
<div className="flex items-center justify-end gap-[0.6rem] px-[1.2rem]">
{statusInfo &&
<div className="data-grid-status-bubble flex flex-wrap items-center gap-x-4 gap-y-1">
<span>진행상황: {
statusInfo.laeSbmsnSttsCd === null || statusInfo.laeSbmsnSttsCd === '0' ?
'미작성'
:
statusInfo.laeSbmsnSttsCd === '1' ? '작성중' : statusInfo.laeSbmsnSttsCd === '2' ? '제출'
:
statusInfo.laeSbmsnSttsCd === '3' ? '승인' : '반려'
}
</span>
<span>제출일: {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)
: '미제출'}</span>
<span>담당자: {statusInfo.laePicNm ? statusInfo.laePicNm : '-'}</span>
<span>연락처: {statusInfo.laePicTelNo ? statusInfo.laePicTelNo : '-'}</span>
</div>
}
{isExcelImport && (
<Button
className="h-[3.2rem] w-[96px] bg-gradient-green px-6 hover:bg-gradient-green-hover"
@@ -1148,48 +1080,64 @@ const DataGrid = ({
)}
</div>
<div className="h-[65vh]">
<RealGridReact
ref={gridRef}
autoGenerateField={true}
layout={columnLayout}
rows={rowData}
onInitialized={(grid) => {
if (!grid) return;
(grid as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef);
const dataProvider = gridRef.current?.dataProvider as unknown as
| { setFields?(f: unknown[]): void; setRows?(r: Record<string, string | number>[]): void; getJsonRow?(r: number): Record<string, unknown> }
| undefined;
if (dataProvider?.setFields) {
dataProvider.setFields(buildFieldsFromColumns(columns));
<RealGridReact
ref={gridRef}
autoGenerateField={true}
layout={columnLayout}
rows={rowData}
onInitialized={(grid) => {
if (!grid) return;
(grid as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef);
const dataProvider = gridRef.current?.dataProvider as unknown as
| { setFields?(f: unknown[]): void; setRows?(r: Record<string, string | number>[]): void; getJsonRow?(r: number): Record<string, unknown> }
| undefined;
if (dataProvider?.setFields) {
dataProvider.setFields(buildFieldsFromColumns(columns));
}
if (dataProvider?.setRows && rowData?.length) {
dataProvider.setRows(rowData);
}
const gridView = gridRef.current?.gridView ?? grid;
if (!gridView?.columnByName) return;
setTimeout(() => {
const gv = gridRef.current?.gridView;
if (!gv?.columnByName) return;
// 공식 문서: 데이터 영역 툴팁 = renderer.showTooltip + onShowTooltip (displayOptions는 Ellipsis용이라 사용 안 함)
if (cellTooltipCallbackRef.current) {
(gv as { onShowTooltip?: (grid: unknown, index: { itemIndex: number; column: string }, value: string) => string | undefined }).onShowTooltip = (_grid, index, value) => {
const row = rowDataRef.current[index?.itemIndex];
if (row == null) return value;
const column = index.column;
const col = columns.find((c) => c.name === column);
const fieldName = col?.fieldName ?? column;
const custom = cellTooltipCallbackRef.current?.({ row: row as Record<string, string | number>, fieldName });
// verificationResult 등 메시지가 있을 때만 툴팁 표시 (없으면 undefined 반환 → 툴팁 안 띄움)
return custom && custom.length > 0 ? custom : undefined;
};
}
if (dataProvider?.setRows && rowData?.length) {
dataProvider.setRows(rowData);
}
const gridView = gridRef.current?.gridView ?? grid;
if (!gridView?.columnByName) return;
setTimeout(() => {
const gv = gridRef.current?.gridView;
if (!gv?.columnByName) return;
columns.forEach((column) => {
const baseStyleName = column.styleName;
const gridCol = gv.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<string, string | number>;
const extra = cellStyleCallbackRef.current?.({ rowIndex: itemIndex, fieldName, row });
if (!extra) return baseStyleName ?? '';
return baseStyleName ? `${extra} ${baseStyleName}` : extra;
};
columns.forEach((column) => {
const baseStyleName = column.styleName;
const gridCol = gv.columnByName(column.name) as { styleCallback?: (g: unknown, dataCell: { itemIndex?: number; index?: { itemIndex?: number }; dataField?: string; fieldName?: string }) => string; renderer?: { showTooltip?: boolean } } | null;
if (gridCol) {
if (cellTooltipCallbackRef.current) {
(gv as { setColumnProperty?: (col: string, key: string, value: unknown) => void }).setColumnProperty?.(column.name, 'renderer', { showTooltip: true });
}
});
gv.refresh();
}, 0);
}}
>
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<string, string | number>;
const extra = cellStyleCallbackRef.current?.({ rowIndex: itemIndex, fieldName, row });
if (!extra) return baseStyleName ?? '';
return baseStyleName ? `${extra} ${baseStyleName}` : extra;
};
}
});
gv.refresh();
}, 0);
}}
>
{columns.map((column) => {
const baseStyleName = column.styleName;
@@ -1202,8 +1150,10 @@ const DataGrid = ({
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.000' })}
header={column.header}
editable={column.editable}
{...(column.width != null && { width: column.width })}
{...(baseStyleName != null && { styleName: baseStyleName })}
{...(column.visible === false && { visible: false })}
{...(cellTooltipCallback && { renderer: { showTooltip: true } })}
footer={column.footer && !Array.isArray(column.footer) ? {
text: column.footer.text,
expression: column.footer.expression,
+9 -5
View File
@@ -66,8 +66,12 @@ const SearchForm = (props: SearchFormProps = {}) => {
bsnmCd: props.defaultValues?.bsnmCd || '',
});
const sggKey: [string, string] | null =
values.sido && values.sido !== "ALL"
? ["sgg-list", values.sido]
: null;
const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
values.sido && values.sido !== "ALL" ? ["sgg-list", values.sido] : null,
sggKey,
getSggListFetcher,
{
revalidateOnFocus: false,
@@ -87,7 +91,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
},
}
);
const sggOptions = sggList?.data ?? [];
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@@ -109,7 +113,6 @@ const SearchForm = (props: SearchFormProps = {}) => {
const handleSidoChange = (value: string) => {
const selectedBsnm = sidoList?.data.sidoList.find((v) => v.sido === value)?.bsnmCd || '';
setValues((prev) => ({
...prev,
sido: value,
@@ -119,9 +122,10 @@ const SearchForm = (props: SearchFormProps = {}) => {
};
const handleSggChange = (value: string) => {
const listForBsnm = sggList?.data ?? [];
const selectedBsnm = value === 'ALL'
? (sidoList?.data.sidoList.find((v) => v.sido === values.sido)?.bsnmCd || '')
: (sggList?.data?.find((v) => v.gugun === value)?.bsnmCd || '');
: (listForBsnm.find((v) => v.gugun === value)?.bsnmCd || '');
setValues((prev) => ({
...prev,
@@ -169,7 +173,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체</SelectItem>
{sggList?.data?.map((sgg) => (
{sggOptions?.map((sgg) => (
<SelectItem key={sgg.adstDstrCd} value={sgg.gugun}>
{sgg.gugun}
</SelectItem>
+88 -87
View File
@@ -57,96 +57,96 @@ function toNum(v: unknown): number {
}
/** 헤더에 정렬 화살표 표시 (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;
getElement?: () => HTMLElement | null;
container?: HTMLElement;
},
columns: { fieldName?: string; name: string }[],
sortDir: Record<string, "asc" | "desc" | "none">,
gridElement?: HTMLElement | null
) {
try {
// 그리드 DOM 요소 찾기
let gridEl: HTMLElement | null = null;
if (gridElement) {
gridEl = gridElement;
} else if (gridView.getElement) {
gridEl = gridView.getElement();
} else if (gridView.container) {
gridEl = gridView.container;
} else {
// 폴백: 모든 그리드 중에서 찾기
const allGrids = document.querySelectorAll('.rg-grid, [data-grid-id]');
if (allGrids.length > 0) {
// 활성 탭의 그리드를 찾기 위해 TabsContent 내부의 그리드 찾기
const activeTabContent = document.querySelector('[data-state="active"]')?.closest('[role="tabpanel"]');
if (activeTabContent) {
gridEl = activeTabContent.querySelector('.rg-grid, [data-grid-id]') as HTMLElement;
}
if (!gridEl && allGrids.length === 1) {
gridEl = allGrids[0] as HTMLElement;
}
}
}
// 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;
// getElement?: () => HTMLElement | null;
// container?: HTMLElement;
// },
// columns: { fieldName?: string; name: string }[],
// sortDir: Record<string, "asc" | "desc" | "none">,
// gridElement?: HTMLElement | null
// ) {
// try {
// // 그리드 DOM 요소 찾기
// let gridEl: HTMLElement | null = null;
// if (gridElement) {
// gridEl = gridElement;
// } else if (gridView.getElement) {
// gridEl = gridView.getElement();
// } else if (gridView.container) {
// gridEl = gridView.container;
// } else {
// // 폴백: 모든 그리드 중에서 찾기
// const allGrids = document.querySelectorAll('.rg-grid, [data-grid-id]');
// if (allGrids.length > 0) {
// // 활성 탭의 그리드를 찾기 위해 TabsContent 내부의 그리드 찾기
// const activeTabContent = document.querySelector('[data-state="active"]')?.closest('[role="tabpanel"]');
// if (activeTabContent) {
// gridEl = activeTabContent.querySelector('.rg-grid, [data-grid-id]') as HTMLElement;
// }
// if (!gridEl && allGrids.length === 1) {
// gridEl = allGrids[0] as HTMLElement;
// }
// }
// }
if (!gridEl) return;
// if (!gridEl) return;
columns.forEach((col) => {
if (!col.fieldName && !col.name) return;
// columns.forEach((col) => {
// if (!col.fieldName && !col.name) return;
const fieldName = col.fieldName || col.name;
const direction = sortDir[fieldName] || sortDir[col.name] || "none";
if (direction === "none") return;
// const fieldName = col.fieldName || col.name;
// const direction = sortDir[fieldName] || sortDir[col.name] || "none";
// if (direction === "none") return;
// 헤더 셀 찾기 (fieldName 또는 name으로)
const headerCells = gridEl.querySelectorAll('.rg-header-cell, [class*="header"], .rg-header');
let targetCell: HTMLElement | null = null;
// // 헤더 셀 찾기 (fieldName 또는 name으로)
// const headerCells = gridEl.querySelectorAll('.rg-header-cell, [class*="header"], .rg-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 === colName ||
text === colFieldName ||
text.includes(colName) ||
(colFieldName && text.includes(colFieldName))
)) {
targetCell = el;
}
});
// headerCells.forEach((cell) => {
// const el = cell as HTMLElement;
// const text = el.textContent?.trim();
// const colName = col.name;
// const colFieldName = col.fieldName;
// // 헤더 텍스트와 컬럼 이름이 일치하는지 확인
// if (text && (
// text === colName ||
// text === colFieldName ||
// text.includes(colName) ||
// (colFieldName && text.includes(colFieldName))
// )) {
// targetCell = el;
// }
// });
if (!targetCell) return;
// if (!targetCell) return;
const headerCell = targetCell as HTMLElement;
// const headerCell = targetCell as HTMLElement;
// 기존 화살표 제거
const existingIndicator = headerCell.querySelector(".rg-sort-indicator") as HTMLElement | null;
if (existingIndicator) {
existingIndicator.remove();
}
// // 기존 화살표 제거
// const existingIndicator = headerCell.querySelector(".rg-sort-indicator") as HTMLElement | null;
// if (existingIndicator) {
// existingIndicator.remove();
// }
// 화살표 추가
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);
}
}
// // 화살표 추가
// 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(
@@ -154,13 +154,13 @@ function createOnSortingHandler(
setRowData: React.Dispatch<React.SetStateAction<Record<string, string | number>[]>>,
sortDirRef: { current: Record<string, "asc" | "desc" | "none"> },
originalRowsRef: { current: Record<string, Record<string, string | number>[]> },
findGridElement?: () => HTMLElement | null
_findGridElement?: () => HTMLElement | null
) {
const fieldNames = [...new Set(columns.map((c) => c.fieldName).filter((x): x is string => typeof x === "string"))];
// mergeCells: true인 컬럼 찾기 (시군구 등) - 그룹 필드로 사용
// 시군구를 우선으로 찾고, 없으면 첫 번째 mergeCells 컬럼 사용
const mergeCellCols = columns.filter((c) => c.mergeCells === true && c.fieldName);
const mergeCellCol = mergeCellCols.find((c) => c.fieldName === 'gugun') || mergeCellCols[0];
const mergeCellCol = mergeCellCols.find((c) => c.fieldName === 'sggNm') || mergeCellCols[0];
const groupField = mergeCellCol?.fieldName || null;
const orderByField = 'displayYear'; // 고정값 또는 설정 가능하게 할 수 있음
const hasMergeCells = mergeCellCols.length > 0;
@@ -419,6 +419,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
target: 'local',
fileName: `${menuName}_${tabLabel}.xlsx`,
footer: 'hidden',
allColumns: true, // 숨김(visible: false) 컬럼도 엑셀에 포함
done: () => {
// 헤더 원복
columns.forEach((col) => {
@@ -708,7 +709,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
valueType: col.valueType,
dataFormat: col.dataFormat,
...(col.styleName != null && { styleName: col.styleName }),
...(col.mergeCells && { mergeRule: { criteria: 'value' as const } }),
...(col.mergeCells && { mergeRule: { criteria: col.mergeGroupField ? `values['${col.mergeGroupField}'] + value` : 'value' as const } }),
};
if (col.footer) {
if (Array.isArray(col.footer)) c.footer = col.footer;
@@ -772,7 +773,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
return (
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full" variant="outline">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full" variant="default">
<TabsList className="mb-4">
{columnInfo.map((info) => {
const tabKey = info.tabKey;
@@ -993,7 +994,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
valueType: col.valueType,
dataFormat: col.dataFormat,
...(col.styleName != null && { styleName: col.styleName }),
...(col.mergeCells && { mergeRule: { criteria: 'value' as const } }),
...(col.mergeCells && { mergeRule: { criteria: col.mergeGroupField ? `values['${col.mergeGroupField}'] + value` : 'value' as const } }),
};
if (col.footer) {
@@ -1151,7 +1152,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
expression: (column.footer as { expression?: 'sum' | 'avg' | 'count' | 'max' | 'min' }).expression,
numberFormat: (column.footer as { numberFormat?: string }).numberFormat,
} : undefined}
mergeRule={column.mergeCells ? { criteria: 'value' } : undefined}
mergeRule={column.mergeCells ? { criteria: column.mergeGroupField ? `values['${column.mergeGroupField}'] + value` : 'value' } : undefined}
sortable={!column.sortdisable}
/>
))}
@@ -7,34 +7,44 @@ import SubmitButton from "@/components/button/SumitButton";
import SaveButton from "@/components/button/SaveButton";
import LastYearDataButton from "@/components/button/LastYearDataButton";
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
import { LoadingWithAnimation } from "@common/components";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import SubmitDailog from "./SubmitDailog";
import { useAlertStore } from "@/shared/lib";
import { statisticsInputAPi } from "@/entities/statistics/api/statistics-input-service";
import { useAdminStore } from "@/store/admin-store";
import { StatisticsInputSewSupplyRateApi } from "@/entities/statistics/api/statistics-input-sew-supply-rate-service";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import type {
SewerageSupplyRateItem,
SewerageSupplyRateRequestParams,
SewerageSupplyRateSaveItem,
SewerageSupplyRateVerificationResult,
SewerageSupplyRateSaveRequest
} from "@/entities/statistics/model/statistics-input-interface";
SewerageSupplyRateSaveRequest,
SewerageSupplyRateStatusInfo,
} from "@/entities/statistics/model/statistics-sewage-supply-rate-input-interface";
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
import { toGridRows } from "@/components/util/grid/gridFunction";
import { getFooterSummary } from "@/components/util/grid/footerSummary";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { doList } from "@/components/util/column/input/DoList";
type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: SewerageSupplyRateVerificationResult;
};
function getSewerageSupplyRateParams(
values: SearchFormValues,
overrides?: { yr?: string; demoYr?: string; mode?: string }
): SewerageSupplyRateRequestParams {
return {
sido: values.sido,
gugun: values.gugun === 'ALL' ? '' : values.gugun,
bsnmCd: values.bsnmCd,
yr: overrides?.yr ?? values.year,
ctpvNm: values.sido,
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
sewerBzmnCd: values.bsnmCd,
statsYr: overrides?.yr ?? values.year,
...(overrides?.mode && { mode: overrides.mode }),
};
}
@@ -47,13 +57,18 @@ const SeweragePenetrationRateInp = () => {
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
const [isEnterable, setIsEnterable] = useState<boolean>(true);
const [lastSearchedYear, setLastSearchedYear] = useState<string | null>(null);
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
const [statusInfo, setStatusInfo] = useState<SewerageSupplyRateStatusInfo | null>(null);
const [showDailog, setShowDailog] = useState<boolean>(false);
const { confirm } = useConfirm();
const [footerFromApi, setFooterFromApi] = useState<{ totalPopl?: number; poplSm?: number; frgnrPoplSm?: number } | null>(null);
const { isAdminInfo } = useAdminStore();
const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null);
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const footerFieldNames = ['totalPopl', 'poplSm', 'frgnrPoplSm'] as const;
const footerWithApiDefault = (fieldName: (typeof footerFieldNames)[number], numberFormat: string) => ({
const _footerFieldNames = ['sewerBzmnWholPpltnCnt', 'sewerBzmnDomplWholPpltnCnt', 'sewerBzmnFrgnrWholPpltnCnt', 'sewerBzmnArea'] as const;
const footerWithApiDefault = (fieldName: (typeof _footerFieldNames)[number], numberFormat: string) => ({
expression: 'sum' as const,
numberFormat,
valueCallback: (grid: Parameters<typeof getFooterSummary>[0], column: Parameters<typeof getFooterSummary>[1]) =>
@@ -63,59 +78,78 @@ const SeweragePenetrationRateInp = () => {
// 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요.
const columnInfo = useMemo(() => {
const { columns, columnLayout } = SeweragePenetrationRateInpColumn;
const base = isEnterable ? columns : columns.map((col) => ({ ...col, editable: false as const }));
const base = (isEnterable || isAdminInfo) ? columns : columns.map((col) => ({ ...col, editable: false as const }));
return {
columnLayout,
columns: base.map((col) => {
if (col.fieldName === 'totalPopl') return { ...col, footer: footerWithApiDefault('totalPopl', '#,##0') };
if (col.fieldName === 'poplSm') return { ...col, footer: footerWithApiDefault('poplSm', '#,##0') };
if (col.fieldName === 'frgnrPoplSm') return { ...col, footer: footerWithApiDefault('frgnrPoplSm', '#,##0') };
if (col.fieldName === 'pbadmsWholPpltnCnt') return { ...col, footer: footerWithApiDefault('sewerBzmnWholPpltnCnt', '#,##0') };
if (col.fieldName === 'domplWholPpltnCnt') return { ...col, footer: footerWithApiDefault('sewerBzmnDomplWholPpltnCnt', '#,##0') };
if (col.fieldName === 'frgnrWholPpltnCnt') return { ...col, footer: footerWithApiDefault('sewerBzmnFrgnrWholPpltnCnt', '#,##0') };
if (col.fieldName === 'sewerBzmnArea') return { ...col, footer: footerWithApiDefault('sewerBzmnArea', '#,##0.000') };
return col;
}),
};
}, [isEnterable, footerFromApi]);
}, [isEnterable, isAdminInfo, footerFromApi]);
const handleReset = () => {
setRowData([]);
setStatusInfo(null);
setLastSearchedYear(null);
setLastSearchedValues(null);
setFooterFromApi(null);
};
const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams) => {
const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams, isEnterable: boolean) => {
try {
setIsLoading(true);
const response = await statisticsInputAPi.getSewerageSupplyRateList(params);
const response = await StatisticsInputSewSupplyRateApi.getSewerageSupplyRateList(params);
if (response.status === 'success' && response.data) {
const commonInfo = response.data.commonInfo;
const statusInfo = response.data.statusInfo;
const convertedData: SewerageSupplyRateItem[] = response.data.sewerageSupplyRateList.map((d) => ({
...d,
sido: commonInfo.sido,
speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr,
piplSupprt: commonInfo.piplSupprt,
ctpvNm: commonInfo.ctpvNm,
spcCtpnRgnCn: d.spcCtpnRgnCn === '' || d.spcCtpnRgnCn === null ? '해당없음' : d.spcCtpnRgnCn,
swagePplPsplyRt: commonInfo.swagePplPsplyRt,
}));
setRowData(
convertedData.map((d) => {
const row = toGridRows([d])[0] as Record<string, string | number> & { verificationResult?: SewerageSupplyRateVerificationResult };
const row = toGridRows([d])[0] as GridRowWithVerification;
if (d.verificationResult) row.verificationResult = d.verificationResult;
return row;
})
);
setStatusInfo(response.data.statusInfo);
setFooterFromApi(
commonInfo
? {
totalPopl: commonInfo.bsnmTotalPopl,
poplSm: commonInfo.bsnmPoplSm,
frgnrPoplSm: commonInfo.bsnmFrgnrPoplSm,
sewerBzmnWholPpltnCnt: commonInfo.sewerBzmnWholPpltnCnt,
sewerBzmnDomplWholPpltnCnt: commonInfo.sewerBzmnDomplWholPpltnCnt,
sewerBzmnFrgnrWholPpltnCnt: commonInfo.sewerBzmnFrgnrWholPpltnCnt,
sewerBzmnArea: commonInfo.sewerBzmnArea,
}
: null
);
if (statusInfo && statusInfo.laeSbmsnSttsCd) {
if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) {
setIsEnterable(false);
} else {
setIsEnterable(isEnterable);
}
} else {
setIsEnterable(isEnterable);
}
} else {
alert(response.message);
}
} catch {
alert('데이터 조회 중 오류가 발생했습니다.');
setRowData([]);
setStatusInfo(null);
setFooterFromApi(null);
} finally {
setIsLoading(false);
@@ -129,13 +163,14 @@ const SeweragePenetrationRateInp = () => {
}
try {
const response = await statisticsInputAPi.checkInputPeriod(values.year || '');
const response = await StatisticsInputSewSupplyRateApi.checkInputPeriod(values.year || '');
if (response.status === 'success') {
setIsEnterable(response.data);
const checkEnterable = response.data;
// setIsEnterable(response.data);
setLastSearchedYear(values.year || null);
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values));
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values), checkEnterable);
setLastSearchedValues(values);
} else {
alert(response.message);
}
@@ -160,26 +195,118 @@ const SeweragePenetrationRateInp = () => {
return;
}
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' }));
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' }), true);
};
const submitFunc = () => {
// 제출 로직
const submitFunc = async () => {
const isError = rowData.filter((d) => d.verificationResult);
if (isError.length > 0) {
alert(`데이터에 오류가 있습니다.
수정 후 제출해주세요.`);
return;
}
if (await confirm('제출 하시겠습니까?')) {
setShowDailog(true);
}
};
const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
try {
setIsLoading(true);
const userInfo = await getUserInfo();
const currentData = getGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert('제출할 데이터가 없습니다.');
return;
}
const columnFormatMap = new Map<string, string>();
SeweragePenetrationRateInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) {
columnFormatMap.set(col.fieldName, col.dataFormat);
}
});
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === '') return 0;
const num = typeof value === 'string' ? parseFloat(value.replace(/,/g, '')) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
sggNm: (d.sggNm as string) || null,
admdstCd: d.admdstCd as string,
admdstNm: d.admdstNm as string,
trbtrNm: d.trbtrNm as string,
spcCtpnRgnCn: d.spcCtpnRgnCn === '해당없음' ? null : (d.spcCtpnRgnCn as string | null),
ts1SwtrmtPpltnCnt: formatNumber(d.ts1SwtrmtPpltnCnt, 'ts1SwtrmtPpltnCnt'),
ts2SwtrmtPpltnCnt: formatNumber(d.ts2SwtrmtPpltnCnt, 'ts2SwtrmtPpltnCnt'),
ts3SwtrmtPpltnCnt: formatNumber(d.ts3SwtrmtPpltnCnt, 'ts3SwtrmtPpltnCnt'),
ts1WwtPrcsPpltnCnt: formatNumber(d.ts1WwtPrcsPpltnCnt, 'ts1WwtPrcsPpltnCnt'),
ts2WwtPrcsPpltnCnt: formatNumber(d.ts2WwtPrcsPpltnCnt, 'ts2WwtPrcsPpltnCnt'),
ts3WwtPrcsPpltnCnt: formatNumber(d.ts3WwtPrcsPpltnCnt, 'ts3WwtPrcsPpltnCnt'),
yetCntnPpltnCnt: formatNumber(d.yetCntnPpltnCnt, 'yetCntnPpltnCnt'),
staaArea: formatNumber(d.staaArea, 'staaArea'),
unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'),
drtwPrcsPpltnCnt: formatNumber(d.drtwPrcsPpltnCnt, 'drtwPrcsPpltnCnt'),
spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'),
staaExcpArea: formatNumber(d.staaExcpArea, 'staaExcpArea'),
rmrk: (d.rmrk as string) ?? '',
wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'),
wholArea: formatNumber(d.wholArea, 'wholArea'),
wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'),
wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'),
wholWwtPrcsPpltnCnt: formatNumber(d.wholWwtPrcsPpltnCnt, 'wholWwtPrcsPpltnCnt'),
wholStaaExcpPpltnCnt: formatNumber(d.wholStaaExcpPpltnCnt, 'wholStaaExcpPpltnCnt'),
staaPsplyRt: formatNumber(d.staaPsplyRt, 'staaPsplyRt'),
altdPrcsPsplyRt: formatNumber(d.altdPrcsPsplyRt, 'altdPrcsPsplyRt'),
sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'),
}));
const requestData: SewerageSupplyRateSaveRequest = {
sewerBzmnCd: searchValues?.bsnmCd ?? '',
ctpvNm: searchValues?.sido ?? '',
statsYr: searchValues?.year ?? '',
userId: userInfo.userUnqId,
submitList: convertedData,
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.sewerBzmnFrgnrWholPpltnCnt,
laePicNm: submitInfo.laePicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
};
const response = await StatisticsInputSewSupplyRateApi.submitSewerageSupplyRate(requestData);
if (response.status === 'success') {
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false);
alert('제출되었습니다.');
} else {
alert('데이터 제출 중 오류가 발생했습니다.');
}
} catch {
alert('데이터 제출 중 오류가 발생했습니다.');
} finally {
setIsLoading(false);
}
};
const saveFunc = async () => {
try {
if (await confirm('저장 하시겠습니까?')) {
setIsLoading(true);
const userInfo = await getUserInfo();
const currentData = getGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert('저장할 데이터가 없습니다.');
return;
}
// 컬럼 정의에서 dataFormat 추출 (소수점 자리수 맞추기 위해)
const columnFormatMap = new Map<string, string>();
SeweragePenetrationRateInpColumn.columns.forEach((col) => {
@@ -193,56 +320,72 @@ const SeweragePenetrationRateInp = () => {
if (value === null || value === undefined || value === '') return 0;
const num = typeof value === 'string' ? parseFloat(value.replace(/,/g, '')) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
// #,##0.000 → 소수점 3자리, #,##0.0 → 소수점 1자리, #,##0 → 정수
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
gugun: (d.gugun as string) || null,
adstCd: d.adstCd as string,
adstDong: d.adstDong as string,
bstrm: d.bstrm as string,
speclCntrplnAr: d.speclCntrplnAr === '해당없음' ? null : (d.speclCntrplnAr as string | null),
pblswrgPrmTrt: formatNumber(d.pblswrgPrmTrt, 'pblswrgPrmTrt'),
pblswrgScdTrt: formatNumber(d.pblswrgScdTrt, 'pblswrgScdTrt'),
pblswrgTertTrt: formatNumber(d.pblswrgTertTrt, 'pblswrgTertTrt'),
wstrPrmTrt: formatNumber(d.wstrPrmTrt, 'wstrPrmTrt'),
wstrScdTrt: formatNumber(d.wstrScdTrt, 'wstrScdTrt'),
wstrTertTrt: formatNumber(d.wstrTertTrt, 'wstrTertTrt'),
notConectPopltn: formatNumber(d.notConectPopltn, 'notConectPopltn'),
swgeProcZoneInnerAr: formatNumber(d.swgeProcZoneInnerAr, 'swgeProcZoneInnerAr'),
townUnprocPopltn: formatNumber(d.townUnprocPopltn, 'townUnprocPopltn'),
townSwttPopltn: formatNumber(d.townSwttPopltn, 'townSwttPopltn'),
townWrrtnPopltn: formatNumber(d.townWrrtnPopltn, 'townWrrtnPopltn'),
swgeProcZoneOuterAr: formatNumber(d.swgeProcZoneOuterAr, 'swgeProcZoneOuterAr'),
rmk: (d.rmk as string) ?? '',
popltnSm: formatNumber(d.popltnSm, 'popltnSm'),
arSm: formatNumber(d.arSm, 'arSm'),
swgeProcZoneInnerSm: formatNumber(d.swgeProcZoneInnerSm, 'swgeProcZoneInnerSm'),
pblswrgPopltnSm: formatNumber(d.pblswrgPopltnSm, 'pblswrgPopltnSm'),
wstrPopltnSm: formatNumber(d.wstrPopltnSm, 'wstrPopltnSm'),
swgeProcZoneOuterSm: formatNumber(d.swgeProcZoneOuterSm, 'swgeProcZoneOuterSm'),
pblswrgPopltnSuprt: formatNumber(d.pblswrgPopltnSuprt, 'pblswrgPopltnSuprt'),
adtrtPopltnSuprt: formatNumber(d.adtrtPopltnSuprt, 'adtrtPopltnSuprt'),
swrgInstlrt: formatNumber(d.swrgInstlrt, 'swrgInstlrt'),
// 행정구역
sggNm: (d.sggNm as string) || null, // 시군구
admdstCd: d.admdstCd as string, // 행정코드
admdstNm: d.admdstNm as string, // 행정구역명
trbtrNm: d.trbtrNm as string, // 지류
spcCtpnRgnCn: d.spcCtpnRgnCn === '해당없음' ? null : (d.spcCtpnRgnCn as string | null), // 특별대책지역
// 공공하수처리인구(명) - 물리적/생물학적/고도처리
ts1SwtrmtPpltnCnt: formatNumber(d.ts1SwtrmtPpltnCnt, 'ts1SwtrmtPpltnCnt'), // 물리적처리
ts2SwtrmtPpltnCnt: formatNumber(d.ts2SwtrmtPpltnCnt, 'ts2SwtrmtPpltnCnt'), // 생물학적처리
ts3SwtrmtPpltnCnt: formatNumber(d.ts3SwtrmtPpltnCnt, 'ts3SwtrmtPpltnCnt'), // 고도처리
// 폐수처리인구(명) - 물리적/생물학적/고도처리
ts1WwtPrcsPpltnCnt: formatNumber(d.ts1WwtPrcsPpltnCnt, 'ts1WwtPrcsPpltnCnt'),
ts2WwtPrcsPpltnCnt: formatNumber(d.ts2WwtPrcsPpltnCnt, 'ts2WwtPrcsPpltnCnt'),
ts3WwtPrcsPpltnCnt: formatNumber(d.ts3WwtPrcsPpltnCnt, 'ts3WwtPrcsPpltnCnt'),
yetCntnPpltnCnt: formatNumber(d.yetCntnPpltnCnt, 'yetCntnPpltnCnt'), // 미접속인구
staaArea: formatNumber(d.staaArea, 'staaArea'), // 하수처리구역 내 면적(㎢)
// 하수처리구역 외
unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'), // 미처리인구
drtwPrcsPpltnCnt: formatNumber(d.drtwPrcsPpltnCnt, 'drtwPrcsPpltnCnt'), // 오수처리인구
spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'), // 정화조인구
staaExcpArea: formatNumber(d.staaExcpArea, 'staaExcpArea'), // 하수처리구역 외 면적(㎢)
rmrk: (d.rmrk as string) ?? '', // 비고
// 합계·소계(계산필드 저장용)
wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'), // 총인구(명)
wholArea: formatNumber(d.wholArea, 'wholArea'), // 총면적(㎢)
wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'), // 하수처리구역 내 합계
wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'), // 공공하수처리인구 소계
wholWwtPrcsPpltnCnt: formatNumber(d.wholWwtPrcsPpltnCnt, 'wholWwtPrcsPpltnCnt'), // 폐수처리인구 소계
wholStaaExcpPpltnCnt: formatNumber(d.wholStaaExcpPpltnCnt, 'wholStaaExcpPpltnCnt'), // 하수처리구역 외 합계
// 보급률(%)
staaPsplyRt: formatNumber(d.staaPsplyRt, 'staaPsplyRt'), // 공공하수처리구역 인구보급률
altdPrcsPsplyRt: formatNumber(d.altdPrcsPsplyRt, 'altdPrcsPsplyRt'), // 고도처리 인구보급률
sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'), // 하수도 설치율
}));
const requestData: SewerageSupplyRateSaveRequest = {
bsnmCd: searchValues?.bsnmCd ?? '',
sido: searchValues?.sido ?? '',
yr: searchValues?.year ?? '',
userId: userInfo.userName,
saveList: convertedData,
sewerBzmnCd: searchValues?.bsnmCd ?? '', // 하수도 사업자 코드
ctpvNm: searchValues?.sido ?? '', // 시도
statsYr: searchValues?.year ?? '', // 통계 연도
userId: userInfo.userUnqId, // 사용자
saveList: convertedData, // 저장 데이터
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.sewerBzmnFrgnrWholPpltnCnt // 행정참고자료 외국인 합계
};
if (isAdminInfo) {
requestData.superAdmin = 'superAdmin';
}
const response = await statisticsInputAPi.saveSewerageSupplyRate(requestData);
console.log(convertedData)
console.log(response);
const response = await StatisticsInputSewSupplyRateApi.saveSewerageSupplyRate(requestData);
if (response.status === 'success') {
fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
alert('저장되었습니다.');
} else {
alert('데이터 저장 중 오류가 발생했습니다.');
}
} else {
// 저장 취소
}
@@ -252,49 +395,62 @@ const SeweragePenetrationRateInp = () => {
setIsLoading(false);
}
};
console.log(footerFromApi);
return (
<>
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} onValuesChange={setSearchValues} />
{rowData.length > 0 &&
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
{isEnterable && <SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />}
{isEnterable && <LastYearDataButton getLastYearDataFunc={() => getLastYearDataFunc(searchValues ?? {})} />}
{isEnterable && <SubmitButton submitFunc={submitFunc} />}
{
// 최고 관리자일땐 저장 가능하게
<SaveButton saveFunc={saveFunc} />
}
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} onValuesChange={setSearchValues} />
{rowData.length > 0 && (isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) || isAdminInfo) && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
{isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) && (
<>
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={() => getLastYearDataFunc(searchValues ?? {})} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{statusInfo?.laeSbmsnSttsCd === '2' && <CancelSubmitButton cnacelSubmitFunc={() => console.log('test')} />}
{(isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) || isAdminInfo) && <SaveButton saveFunc={saveFunc} />}
</div>
</div>
</div>
}
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>}
<DataGrid
key={isEnterable ? 'enterable' : 'readonly'}
columnInfo={columnInfo}
isExcelImport={(rowData.length > 0) && isEnterable}
rowStartNum={3}
fixedColumns={1}
rowData={rowData}
setRowData={setRowData}
getGridDataRef={getGridDataRef}
cellStyleCallback={({ row, fieldName }) => {
const verification = (row as any).verificationResult as SewerageSupplyRateVerificationResult | undefined;
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
return 'rg-cell-verify-error';
}
}}
/>
</div>
<ConfirmDialog isOpen={false} />
)}
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>}
<DataGrid
key={(isEnterable || isAdminInfo) ? 'enterable' : 'readonly'}
columnInfo={columnInfo}
isExcelImport={(rowData.length > 0) && isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true)}
rowStartNum={3}
fixedColumns={1}
rowData={rowData}
setRowData={setRowData}
statusInfo={statusInfo}
getGridDataRef={getGridDataRef}
cellStyleCallback={({ row, fieldName }) => {
const verification = (row as GridRowWithVerification).verificationResult;
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
return 'rg-cell-verify-error';
}
}}
cellTooltipCallback={({ row, fieldName }) => {
const verification = (row as GridRowWithVerification).verificationResult;
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
return errors.map((e) => e.rsnExpln).filter(Boolean).join('\n');
}
}}
/>
</div>
<ConfirmDialog isOpen={false} />
<SubmitDailog open={showDailog} onOpenChange={setShowDailog} statusInfo={statusInfo} onConfirm={executeSubmit} />
</>
);
};
@@ -1,14 +1,59 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
import { DataGrid } from "@rsone/components";
// import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid";
import ApproveButton from "@/components/button/ApproveButton";
import RejectButton from "@/components/button/RejectButton";
import { LoadingWithAnimation } from "@common/components";
import { StatsInputStatusInpColumn } from "@/components/util/column/input/StatsInputStatusInpColumn";
import { StatisticsInputStatusApi } from "@/entities/statistics/api/statistics-input-status-service";
import type { StatsInputStatusListRequestParams } from "@/entities/statistics/model/statistics-input-status-interface";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { useAlertStore } from "@/shared/lib";
import { toGridRows } from "@/components/util/grid/gridFunction";
// 하수도 통계 관리 > 00. 통계 입력 현황
const StatsInputStatusInp = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleSearch = async (values: SearchFormValues) => {
if (values.sido === '' || values.sido === 'ALL') {
alert('시도를 선택해주세요.');
return;
}
const searchParam: StatsInputStatusListRequestParams = {
ctpvNm: values.sido,
sewerBzmnCd: values.bsnmCd,
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
statsYr: values.year,
};
try {
setIsLoading(true);
const response = await StatisticsInputStatusApi.getStatisticsInputStatusList(searchParam);
console.log(response)
if (response.status === 'success' && response.data) {
setRowData(toGridRows(response.data));
} else {
alert(response.message);
}
} catch {
alert('데이터 조회 중 오류가 발생했습니다.');
setRowData([]);
} finally {
setIsLoading(false);
}
};
const handleReset = () => {
setRowData([]);
};
const rejectFunc = () => {
// 보완 요청 로직
@@ -18,25 +63,31 @@ const StatsInputStatusInp = () => {
// 승인 요청 로직
};
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
return (
<>
<SearchForm />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<RejectButton rejectFunc={rejectFunc} />
<ApproveButton approveFunc={approveFunc} />
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<RejectButton rejectFunc={rejectFunc} />
<ApproveButton approveFunc={approveFunc} />
</div>
</div>
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>}
<DataGrid
columnInfo={ StatsInputStatusInpColumn }
rowStartNum={2}
fixedColumns={3}
rowData={rowData}
setRowData={setRowData}
isExcelImport={false}
showHeaderCheckbox={true}
/>
</div>
<DataGrid
columnInfo={ StatsInputStatusInpColumn }
rowStartNum={2}
fixedColumns={3}
rowData={rowData}
setRowData={setRowData}
isExcelImport={false}
/>
</>
);
};
@@ -0,0 +1,167 @@
"use client";
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Input,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "@common/components";
import { useEffect } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { statisticsInputSubmitInfoSchema, type StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
import { type SewerageSupplyRateStatusInfo } from "@/entities/statistics/model/statistics-sewage-supply-rate-input-interface";
/** 숫자만 추출 후 하이픈 포맷 (010-1234-5678, 02-123-4567 등) */
function formatPhoneWithHyphen(value: string): string {
const digits = value.replace(/\D/g, "");
if (digits.length <= 3) return digits;
if (digits.startsWith("010")) {
if (digits.length <= 7) return `010-${digits.slice(3)}`;
return `010-${digits.slice(3, 7)}-${digits.slice(7, 11)}`;
}
if (digits.startsWith("02")) {
if (digits.length <= 5) return `02-${digits.slice(2)}`;
if (digits.length <= 9) return `02-${digits.slice(2, 5)}-${digits.slice(5, 9)}`;
return `02-${digits.slice(2, 6)}-${digits.slice(6, 10)}`;
}
if (digits.length <= 6) return `${digits.slice(0, 3)}-${digits.slice(3)}`;
return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6, 10)}`;
}
const PHONE_FIELDS: (keyof StatisticsInputSubmitInfoFormData)[] = ["laePicTelNo", "laeIdfrTelNo"];
interface SubmitDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
statusInfo: SewerageSupplyRateStatusInfo | null;
/** 다이얼로그에서 저장 클릭 시 호출. 제출 정보를 넘기면 부모에서 requestData에 포함해 제출 API 호출 */
onConfirm?: (data: StatisticsInputSubmitInfoFormData) => void | Promise<void>;
}
const defaultValues: StatisticsInputSubmitInfoFormData = {
laePicNm: "",
laeKbrdrJbgdNm: "",
laeKbrdrDeptNm: "",
laePicTelNo: "",
laeIdfrNm: "",
laeIdfrJbgdNm: "",
laeIdfrDeptNm: "",
laeIdfrTelNo: "",
};
function statusInfoToFormData(info: SewerageSupplyRateStatusInfo): StatisticsInputSubmitInfoFormData {
return {
laePicNm: info.laePicNm ?? "",
laeKbrdrJbgdNm: info.laeKbrdrJbgdNm ?? "",
laeKbrdrDeptNm: info.laeKbrdrDeptNm ?? "",
laePicTelNo: info.laePicTelNo ?? "",
laeIdfrNm: info.laeIdfrNm ?? "",
laeIdfrJbgdNm: info.laeIdfrJbgdNm ?? "",
laeIdfrDeptNm: info.laeIdfrDeptNm ?? "",
laeIdfrTelNo: info.laeIdfrTelNo ?? "",
};
}
const SubmitDailog = ({ open, onOpenChange, statusInfo, onConfirm }: SubmitDialogProps) => {
const { control, handleSubmit, reset, formState: { errors } } = useForm<StatisticsInputSubmitInfoFormData>({
resolver: zodResolver(statisticsInputSubmitInfoSchema),
defaultValues,
});
useEffect(() => {
if (open) {
reset(statusInfo ? statusInfoToFormData(statusInfo) : defaultValues);
}
}, [open, statusInfo, reset]);
async function onSubmit(data: StatisticsInputSubmitInfoFormData) {
await onConfirm?.(data);
onOpenChange(false);
reset(defaultValues);
}
function handleCancel() {
onOpenChange(false);
reset(defaultValues);
}
const rows: { name: keyof StatisticsInputSubmitInfoFormData; label: string; placeholder: string }[] = [
{ name: "laePicNm", label: "작성자 명", placeholder: "작성자 명 입력" },
{ name: "laeKbrdrJbgdNm", label: "작성자 직제", placeholder: "작성자 직제 입력" },
{ name: "laeKbrdrDeptNm", label: "작성자 부서", placeholder: "작성자 부서 입력" },
{ name: "laePicTelNo", label: "작성자 연락처", placeholder: "작성자 연락처 입력" },
{ name: "laeIdfrNm", label: "승인자명", placeholder: "승인자명 입력" },
{ name: "laeIdfrJbgdNm", label: "승인자 직제", placeholder: "승인자 직제 입력" },
{ name: "laeIdfrDeptNm", label: "승인자 부서", placeholder: "승인자 부서 입력" },
{ name: "laeIdfrTelNo", label: "승인자 연락처", placeholder: "승인자 연락처" },
];
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[800px]">
<DialogHeader>
<DialogTitle>제출 정보</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit(onSubmit)}>
<Table variant="secondary">
<TableBody>
{rows.map(({ name, label, placeholder }) => (
<TableRow key={name}>
<TableHead className="w-[150px] bg-muted font-medium" data-required>
{label}
</TableHead>
<TableCell>
<Controller
name={name}
control={control}
render={({ field }) => {
const isPhone = PHONE_FIELDS.includes(name);
return (
<div className="space-y-1">
<Input
{...field}
value={field.value ?? ""}
placeholder={placeholder}
onChange={(e) => {
const v = e.target.value;
field.onChange(isPhone ? formatPhoneWithHyphen(v) : v);
}}
/>
{errors[name]?.message && (
<p className="text-sm text-destructive ml-1">
{errors[name]?.message}
</p>
)}
</div>
);
}}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<DialogFooter className="mt-6">
<Button type="button" color="gray" onClick={handleCancel}>
취소
</Button>
<Button type="submit" color="green">
저장
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
};
export default SubmitDailog;
@@ -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 (
<div className="relative">
<div className="card card-border">
<form>
<div className="flex items-center gap-8 p-2">
<div className="flex items-center gap-2">
<Label className="text-label">
통계항목
</Label>
{/* <Select value={lwpoCd} onValueChange={setLwpoCd}>
<SelectTrigger className="w-[22rem]">
<SelectValue placeholder="전체"></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체</SelectItem>
{lwpoCdList?.content?.map((l) => (
<SelectItem key={l.lwpoCd} value={l.lwpoCd}>
{l.lwpoCdNm}
</SelectItem>
))}
</SelectContent>
</Select> */}
<CodeSelect
upCd="STITEM"
value={statsCd}
onValueChange={setStatsCd}
placeholder="선택"
className="w-[16rem]"
/>
</div>
<div className="flex items-center gap-2 ml-auto">
<Button color="green" className="h-[3.2rem] w-[96px]" type="button">
초기화
</Button>
<Button className="h-[3.2rem] w-[96px]" type="submit">
조회
</Button>
</div>
</div>
</form>
</div>
<DataGrid
columnInfo={LogHistoryColumn}
// rowStartNum={1}
// fixedColumns={1}
isExcelImport={false}
// showHeaderCheckbox={true}
rowData={[]}
setRowData={() => {}}
/>
</div>
);
};
export default LogHistory;
@@ -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<Record<string, string | number>[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(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 (
<>
<SearchForm fctName={true} />
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
@@ -29,8 +85,24 @@ const PumpingStationRvw = () => {
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<DataGrid columnInfo={PumpingStationInfoList} isExcelImport={false} rowStartNum={3} fixedColumns={3} rowData={rowData} setRowData={setRowData} />
</>
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
<DataGrid
columnInfo={columnInfo}
isExcelImport={false}
fixedColumns={columnInfo.fixedColumns}
rowStartNum={columnInfo.rowStartNum}
rowData={rowData}
setRowData={setRowData}
/>
</div>
);
};
@@ -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<Map<string, Record<string, string | number>[]>>(new Map());
const [isLoading, setIsLoading] = useState<boolean>(false);
const [activeTab, setActiveTab] = useState<string>('ST00030001');
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
const searchParamRef = useRef<SewerPipeMtcRequestParams | null>(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<string, Record<string, string | number>[]>();
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 (
<>
<SearchForm fctName={true} />
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
<ReviewOpinionButton reviewOpinionFunc={() => { }} />
<ApproveButton approveFunc={() => { }} />
<ReturnButton returnFunc={() => { }} />
<SaveButton saveFunc={() => { }} />
</div>
</div>
<TabDataGrid columnInfo={SewagePipeMaintainColumnInfoList} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
<TabDataGrid
columnInfo={columnInfo}
isExcelImport={false}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
activeTab={activeTab}
onActiveTabChange={handleActiveTabChange}
/>
</div>
);
};
@@ -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<Map<string, Record<string, string | number>[]>>(new Map());
const [isLoading, setIsLoading] = useState<boolean>(false);
const [activeTab, setActiveTab] = useState<string>('a');
const [activeTab, setActiveTab] = useState<string>('ST00020001');
const searchParamRef = useRef<SearchFormValues | null>(null);
const searchParamRef = useRef<SewagePipeReviewRequestParams | null>(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<string, Record<string, string | number>[]>();
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[]));
@@ -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<Map<string, Record<string, string | number>[]>>(new Map());
const [isLoading, setIsLoading] = useState<boolean>(false);
const [activeTab, setActiveTab] = useState<string>('ST00040001');
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
const searchParamRef = useRef<SearchFormValues | null>(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<string, Record<string, string | number>[]>();
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 (
<>
<SearchForm fctName={true} />
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset}/>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
<ReviewOpinionButton reviewOpinionFunc={() => { /* 검토의견 로직 */ }} />
<ApproveButton approveFunc={() => { /* 승인 로직 */ }} />
<ReturnButton returnFunc={() => { /* 반려 로직 */ }} />
<SaveButton saveFunc={() => { /* 저장 로직 */ }} />
</div>
</div>
<TabDataGrid columnInfo={SewageReservoirPumpInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
{isLoading && (
<div className="absolute inset-0 z-50 rounded-lg flex justify-center items-center bg-white/50">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
<TabDataGrid
columnInfo={columnInfo}
isExcelImport={false}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
activeTab={activeTab}
onActiveTabChange={handleActiveTabChange}
/>
</div>
);
};
export default SewageReservoirPumpRvw;
export default SewageReservoirPumpRvw;
@@ -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<any[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(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 (
<div className="relative">
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
@@ -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<any[]>([]);
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(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 || '미확인',
}));
@@ -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 (
<div className="relative">
<div className="card card-border">
<form>
<div className="flex items-center gap-8 p-2">
<div className="flex items-center gap-2">
<Label className="text-label">
통계항목
</Label>
<CodeSelect
upCd="STITEM"
value={statsCd}
onValueChange={setStatsCd}
placeholder="선택"
className="w-[16rem]"
/>
</div>
<div className="flex items-center gap-2 ml-auto">
<Button color="green" className="h-[3.2rem] w-[96px]" type="button">
초기화
</Button>
<Button className="h-[3.2rem] w-[96px]" type="submit">
조회
</Button>
</div>
</div>
</form>
</div>
<div className='card !mb-0 flex gap-5 !p-0'>
<div className='card card-border w-1/2 !mx-0 flex flex-col'>
<Table variant="default">
<TableRow>
<TableHead>No</TableHead>
<TableHead>검증항목</TableHead>
<TableHead>등록자</TableHead>
<TableHead>등록일</TableHead>
<TableHead>사용여부</TableHead>
</TableRow>
<TableBody className='text-center'>
<TableRow>
<TableCell>
1
</TableCell>
<TableCell>
인구수검증
</TableCell>
<TableCell>
관리자
</TableCell>
<TableCell>
2026-02-09
</TableCell>
<TableCell>
Y
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<div className='w-1/2'>
</div>
</div>
</div>
);
};
export default GeneralVerificationManagement;
File diff suppressed because it is too large Load Diff
@@ -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<HbVerificationDetailProps> = ({
anlsSn,
onBack,
}) => {
const [selectedVrfcTrgtSn, setSelectedVrfcTrgtSn] = useState<string | null>(
null,
);
const [selectedSido, setSelectedSido] = useState<string | null>(null);
return (
<div className="flex flex-col">
<div className="flex items-center gap-4 p-4">
<Button
variant="ghost"
size="icon"
color="white"
onClick={onBack}
className=""
>
<i className="diveicon di-arrow-back" />
<span className="sr-only">목록으로 돌아가기</span>
</Button>
<h2 className="text-[1.8rem] font-bold text-title">
검증 상세
</h2>
</div>
<div className="flex flex-col gap-4">
<div className="h-[20rem] overflow-y-auto">
<AreaAVerificationList
anlsSn={anlsSn}
selectedVrfcTrgtSn={selectedVrfcTrgtSn}
onSelect={setSelectedVrfcTrgtSn}
/>
</div>
<div className="card card-border !m-4">
<h3 className="p-4 pb-0 text-[1.8rem] font-bold text-title">
검증 결과 상세
</h3>
<div className="flex">
<div className="flex-[0.5] p-4">
<AreaBRegionalGrid
anlsSn={anlsSn}
vrfcTrgtSn={selectedVrfcTrgtSn}
selectedSido={selectedSido}
onSidoSelect={setSelectedSido}
/>
</div>
<div className="flex flex-[0.5] flex-col gap-4 p-4 pt-[4.8rem]">
<div className="flex-[0.5]">
<AreaDIndicators
anlsSn={anlsSn}
vrfcTrgtSn={selectedVrfcTrgtSn}
/>
</div>
<div className="flex-[0.5]">
<AreaCScatterChart
anlsSn={anlsSn}
vrfcTrgtSn={selectedVrfcTrgtSn}
selectedSido={selectedSido}
/>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default HbVerificationDetail;
@@ -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<IndicatorsProps> = ({ 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 (
<div className="card card-border flex min-h-[10rem] items-center justify-center gap-6 text-[1.4rem] text-muted-foreground">
항목을 선택해주세요
</div>
);
}
if (isLoading) {
return (
<div className="card card-border flex min-h-[10rem] items-center justify-center gap-6 text-[1.4rem] text-muted-foreground">
지표 데이터를 불러오는 중...
</div>
);
}
const IndicatorItem = ({
label,
value,
}: {
label: string;
value: number | null | undefined;
}) => (
<div className="flex flex-1 flex-col gap-2 overflow-hidden rounded border bg-gray-50 p-2">
<h4
className="truncate text-[1.2rem] font-medium text-label"
title={label}
>
{label}
</h4>
<div className="truncate text-[1.6rem] font-bold">
{value != null ? Number(value).toFixed(4) : "-"}
</div>
</div>
);
return (
<div className="card card-border !m-0 flex min-h-[10rem] flex-wrap gap-2">
<IndicatorItem label="r 중위수" value={dtl?.rtCnterVl} />
<IndicatorItem
label="(E_m)E 중위수"
value={dtl?.lastTrsfScrCnterVl}
/>
<IndicatorItem
label="(E_l)E 하한값"
value={dtl?.lastTrsfScrLlmtVl}
/>
<IndicatorItem
label="(E_u)E 상한값"
value={dtl?.lastTrsfScrUplmtVl}
/>
<IndicatorItem label="E_D_L" value={dtl?.llmtVl} />
<IndicatorItem label="E_D_U" value={dtl?.uplmtVl} />
<IndicatorItem label="이상치 하한선" value={dtl?.otlrLlmtLine} />
<IndicatorItem label="이상치 상한선" value={dtl?.otlrUplmtLine} />
</div>
);
};
export default Indicators;
@@ -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<RegionalGridProps> = ({
anlsSn,
vrfcTrgtSn,
selectedSido,
onSidoSelect,
}) => {
const [selectedGugun, setSelectedGugun] = useState<string>("ALL");
const [selectedStatus, setSelectedStatus] = useState<string>("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<StAnlsRsltResponse>[] = 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 (
<span className="font-medium text-green-600">
정상
</span>
);
if (val === "n")
return (
<span className="font-medium text-red-500">
이상치
</span>
);
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 (
<div className="flex flex-col gap-2">
<div className="flex justify-between">
<div className="flex items-center gap-2">
<Select
value={selectedSido || "ALL"}
onValueChange={handleSidoChange}
>
<SelectTrigger className="w-[12rem] bg-white">
<SelectValue placeholder="시도 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체 (시도)</SelectItem>
{sidoOptions.map((opt) => (
<SelectItem key={opt.lwpoCd} value={opt.lwpoCd}>
{opt.lwpoCdNm}
</SelectItem>
))}
</SelectContent>
</Select>
<Select
value={selectedGugun}
onValueChange={setSelectedGugun}
disabled={!selectedSido || selectedSido === "ALL"}
>
<SelectTrigger className="w-[12rem] bg-white">
<SelectValue placeholder="시군구 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체 (시군구)</SelectItem>
{gugunOptions.map((opt) => (
<SelectItem key={opt.lwpoCd} value={opt.lwpoCd}>
{opt.lwpoCdNm}
</SelectItem>
))}
</SelectContent>
</Select>
<Select
value={selectedStatus}
onValueChange={setSelectedStatus}
>
<SelectTrigger className="w-[10rem] bg-white">
<SelectValue placeholder="상태" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체 상태</SelectItem>
<SelectItem value="NORMAL">정상</SelectItem>
<SelectItem value="OUTLIER">이상치</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex gap-2">
<Button onClick={() => refetch()}>조회</Button>
<Button color="gray">다운로드</Button>
</div>
</div>
<div className="flex-1 overflow-auto">
<DataTable
table={table}
variant="secondary"
viewPagination={true}
className="whitespace-nowrap text-[1.2rem] [&_td]:!px-2 [&_th]:!px-2"
/>
{isLoading && (
<div className="p-4 text-center text-[1.3rem] text-muted-foreground">
데이터를 불러오는 중...
</div>
)}
{!isLoading && (!vrfcTrgtSn || rowData.length === 0) && (
<div className="p-4 text-center text-[1.3rem] text-muted-foreground">
{vrfcTrgtSn
? "표시할 항목이 없습니다."
: "항목을 선택해주세요"}
</div>
)}
</div>
</div>
);
};
export default RegionalGrid;
@@ -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<ScatterChartProps> = ({
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 (
<div className="relative flex-1 p-4">
{!vrfcTrgtSn ? (
<div className="flex h-full w-full items-center justify-center text-[1.4rem] text-muted-foreground">
항목을 선택해주세요
</div>
) : isLoading ? (
<div className="flex h-full w-full items-center justify-center text-[1.4rem] text-muted-foreground">
차트를 불러오는 중...
</div>
) : chartConfig ? (
<div className="h-[40rem] w-full">
<Scatter
data={chartConfig.data as any}
options={chartConfig.options as any}
/>
</div>
) : (
<div className="flex h-full w-full items-center justify-center text-[1.4rem] text-muted-foreground">
표시할 데이터가 없습니다.
</div>
)}
</div>
);
};
export default ScatterChart;
@@ -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<VerificationListProps> = ({
anlsSn,
selectedVrfcTrgtSn,
onSelect,
}) => {
const { data: verificationTargetCodes } = useQuery({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG, "codes"],
queryFn: () =>
verificationTargetManagementApi.getVerificationTargetCodes(),
});
const targetCodeMap = useMemo(() => {
const map = new Map<string, string>();
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 (
<div className="flex-1 px-4 py-2">
{isLoading && (
<div className="p-4 text-center text-[1.3rem] text-muted-foreground">
데이터를 불러오는 중...
</div>
)}
{!isLoading && rowData.length === 0 && (
<div className="p-4 text-center text-[1.3rem] text-muted-foreground">
표시할 항목이 없습니다.
</div>
)}
{!isLoading && rowData.length > 0 && (
<DataTable
table={table}
variant="secondary"
onRowClick={handleRowClick}
viewPagination={false}
className="whitespace-nowrap text-[1.2rem] [&_td]:!px-2 [&_th]:!px-2"
/>
)}
</div>
);
};
export default VerificationList;
+15 -1
View File
@@ -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<string, string | number>; 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<string, string | number>[]) | null;
};
onRowClick?: (rowData: Record<string, string | number>) => void; // 행 클릭 이벤트
statusInfo?: SewerageSupplyRateStatusInfo | null;
};
export type TabDataGridProps = {
@@ -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",
];
@@ -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',
]
};
@@ -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'
],
},
]
@@ -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'
]
};
@@ -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<string>();
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<string>();
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<string>();
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<string>();
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"
}))
};
@@ -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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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"
}))
}));
@@ -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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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"
}))
}));
@@ -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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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"
}))
}));
@@ -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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>();
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"
}))
};
@@ -115,6 +115,7 @@ export const StatsInputStatusInfoList = {
...StatsInputStatusInfoColumnRaw,
columns: StatsInputStatusInfoColumnRaw.columns.map(col => ({
...col,
editable: false
editable: false,
styleName: "rg-cell-readonly"
}))
};
@@ -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),
},
];
@@ -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<AnalysisResponseDto> {
const response = await client.post<AnalysisResponseDto>(
`${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/analyze`,
payload,
);
return response.data;
}
public async getJobStatus(jobId: string): Promise<JobStatusDto> {
const response = await client.get<JobStatusDto>(
`${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/status/${jobId}`,
);
return response.data;
}
public async getResult(jobId: string): Promise<AnalysisResultDto> {
const response = await client.get<AnalysisResultDto>(
`${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/result/${jobId}`,
);
return response.data;
}
}
export const analysisApi = new AnalysisApiService();
@@ -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<StAnlsMstListApiResponse> {
const response = await client.get<StAnlsMstListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}`,
{
params,
},
);
return response.data;
}
public async getOutlierList(
anlsSn: number,
): Promise<StAnlsOutlierListResponse[]> {
const response = await client.get<StAnlsOutlierListResponse[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/outlierList/${anlsSn}`,
);
return response.data;
}
}
export const analysisHistoryApi = new AnalysisHistoryApiService();
@@ -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<StAnlsDtlListApiResponse> {
const response = await client.get<StAnlsDtlListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}`,
{
params,
},
);
return response.data;
}
public async getDetail(
anlsSn: number,
vrfcTrgtSn: number,
): Promise<StAnlsDtlResponse> {
const response = await client.get<StAnlsDtlResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/${anlsSn}/${vrfcTrgtSn}`,
);
return response.data;
}
}
export const stAnlsDtlApi = new StAnlsDtlApiService();
@@ -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<StAnlsRsltListApiResponse> {
const response = await client.get<StAnlsRsltListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT}`,
{
params,
},
);
return response.data;
}
public async getChartData(
anlsSn: number,
vrfcTrgtSn: number,
): Promise<ChartRetDto> {
const response = await client.get<ChartRetDto>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT}/chartList/${anlsSn}/${vrfcTrgtSn}`,
);
return response.data;
}
}
export const stAnlsRsltApi = new StAnlsRsltApiService();
@@ -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<SewerageSupplyRateApiResponse> {
const response = await axios.get<SewerageSupplyRateApiResponse> (
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`,
{
params: params
}
);
return response.data;
}
public async checkInputPeriod(params: string): Promise<CheckInputPeriodResponse> {
const response = await axios.get<CheckInputPeriodResponse> (
`${ENDPOINT.ST_SERVICE.INPUTCOMMON}/check-input-period?year=${params}`,
);
return response.data;
}
public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse> {
const response = await axios.post<SewerageSupplyRateSaveApiResponse> (
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`,
params
);
return response.data;
}
}
export const statisticsInputAPi = new StatisticsInputAPiService();
@@ -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<SewerageSupplyRateApiResponse> {
const response = await stClient.get<SewerageSupplyRateApiResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`,
{ params }
);
return response.data;
}
public async checkInputPeriod(params: string): Promise<CheckInputPeriodResponse> {
const response = await stClient.get<CheckInputPeriodResponse>(
`${ENDPOINT.ST_SERVICE.INPUTCOMMON}/check-input-period?year=${params}`,
);
return response.data;
}
public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse> {
const response = await stClient.post<SewerageSupplyRateSaveApiResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`,
params,
);
return response.data;
}
public async submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse> {
const response = await stClient.post<SewerageSupplyRateSaveApiResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate/submit`,
params,
);
return response.data;
}
}
export const StatisticsInputSewSupplyRateApi = new StatisticsInputSewSupplyRateApiService();
@@ -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<StatsInputStatusListApiResponse> {
const response = await stClient.get<StatsInputStatusListApiResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/stats-input-status`,
{ params: param },
);
return response.data;
}
}
export const StatisticsInputStatusApi = new StatisticsInputStatusApiService();
@@ -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<StatsInputStatusReviewApiResponse> {
const response = await axios.get<StatsInputStatusReviewApiResponse>(
const response = await stClient.get<StatsInputStatusReviewApiResponse>(
`${ENDPOINT.ST_SERVICE.REVIEW}/input-status`,
{ params }
);
return response.data;
}
/**
* 입력 기간 확인 (공통)
* @param year 연도
*/
public async checkInputPeriod(year: string | number): Promise<any> {
const response = await axios.get<any>(
`${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<SewerageSupplyRateReviewApiResponse> {
const response = await axios.get<SewerageSupplyRateReviewApiResponse>(
const response = await stClient.get<SewerageSupplyRateReviewApiResponse>(
`${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<SewagePipeReviewRegionalApiResponse> {
const response = await axios.get<SewagePipeReviewRegionalApiResponse>(
`${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/regional-status`,
const response = await stClient.get<SewagePipeReviewRegionalApiResponse>(
`${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<SewagePipeFacilityApiResponse> {
const response = await stClient.get<SewagePipeFacilityApiResponse>(
`${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<SewagePipeFacilityApiResponse> {
const response = await axios.get<SewagePipeFacilityApiResponse>(
`${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/facility-status`,
): Promise<SewerPipeTypeApiResponse> {
const response = await stClient.get<SewerPipeTypeApiResponse>(
`${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<SewerPipeDiamApiResponse> {
const response = await stClient.get<SewerPipeDiamApiResponse>(
`${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<SewerPipeMtcApiResponse> {
const response = await stClient.get<SewerPipeMtcApiResponse>(
`${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<SewerPipeDrdgApiResponse> {
const response = await stClient.get<SewerPipeDrdgApiResponse>(
`${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<SewerReservoirRegionalApiResponse> {
const response = await stClient.get<SewerReservoirRegionalApiResponse>(
`${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<PumpingStationRegionalApiResponse> {
const response = await stClient.get<PumpingStationRegionalApiResponse>(
`${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<MediationPumpStationApiResponse> {
const response = await stClient.get<MediationPumpStationApiResponse>(
`${ENDPOINT.ST_SERVICE.REVIEW}/mediation-pump-stations`,
{ params }
);
return response.data;
@@ -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<ApiResponse<SearchCondition>> {
const response = await axios.get<ApiResponse<SearchCondition>>(
const response = await stClient.get<ApiResponse<SearchCondition>>(
`${ENDPOINT.ST_SERVICE.COMMON}/search-condition`,
);
return response.data;
};
}
public async getSggList(sidoName: string): Promise<ApiResponse<Sgg[]>> {
const response = await axios.get<ApiResponse<Sgg[]>>(
const response = await stClient.get<ApiResponse<Sgg[]>>(
`${ENDPOINT.ST_SERVICE.COMMON}/sgg`,
{
params: {
sidoName: sidoName,
},
}
{ params: { sidoName } },
);
return response.data;
}
}
@@ -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<VerificationTargetCodeResponse[]>(
`${ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG}/code`,
);
return response.data;
}
/**
* 검증 대상 등록
* @param request 등록 요청 데이터
@@ -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<StAnlsMstResponse>;
@@ -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<string, AnalysisSummaryDto> | null;
outliers: OutlierRecordDto[];
results: OutlierRecordDto[];
totalCount: number;
cached: boolean;
processingTimeMs: number;
anlsSn: number;
}
export type AnalysisHistoryListApiResponse = PageableList<AnalysisResultDto>;
@@ -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<SewerReservoirRegionalApiResponse>;
}
/**
* 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<SewerReservoirRegionalItem[]>;
export type PumpingStationRegionalApiResponse = ApiResponse<PumpingStationRegionalItem[]>;
/**
* 하수저류시설 및 빗물펌프장 서비스 인터페이스
*/
export interface SewerReservoirService {
/**
* 하수저류시설 리스트 조회 (Tab1)
*/
getSewerReservoirList(params: {
yr?: string;
bsnmCd?: string;
sido?: string;
gugun?: string;
}): Promise<SewerReservoirRegionalApiResponse>;
/**
* 빗물펌프장 리스트 조회 (Tab2)
*/
getPumpStationList(params: {
yr?: string;
bsnmCd?: string;
sido?: string;
gugun?: string;
}): Promise<PumpingStationRegionalApiResponse>;
}
@@ -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<StAnlsDtlResponse>;
@@ -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<StAnlsRsltResponse>;
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)
}
@@ -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<string, VerificationResultItem[]>;
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<SewerageSupplyRateResponseData> & {
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<SewerageSupplyRateSaveData>;
// 하수도 통계 입력 기간 조회
export interface CheckInputPeriodResponse {
status: string;
message: string;
data: boolean;
}
export interface StatisticsInputService {
getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise<SewerageSupplyRateApiResponse>;
checkInputPeriod(params: string): Promise<CheckInputPeriodResponse>;
saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse>;
}
@@ -104,7 +104,7 @@ export type StatsInputStatusReviewApiResponse = ApiResponse<StatsInputStatusRevi
export interface StatsInputStatusReviewService {
getStatsInputStatusList(params: {
yr?: String;
yr?: string;
bsnmCd?: string;
sido?: string;
gugun?: string;
@@ -0,0 +1,34 @@
import type { ApiResponse } from "./statistics-interface";
export interface StatsInputStatusListRequestParams {
ctpvNm?: string;
sggNm?: string;
sewerBzmnCd?: string;
statsYr?: string;
mode?: string;
}
export interface StatsInputStatusList {
ctpvNm?: string;
sggNm?: string;
statsCd?: string;
menuTabName?: string;
locgStatusNm?: string;
inputRate?: number;
laePicNm?: string;
laePicTelNo?: string;
laeKbrdrDeptNm?: string;
laeKbrdrJbgdNm?: string;
laeIdfrNm?: string;
laeIdfrDeptNm?: string;
laeIdfrJbgdNm?: string;
rjctOpnnCn?: string;
}
export type StatsInputStatusListApiResponse = ApiResponse<StatsInputStatusList[]> & {
message: string;
}
export interface StatisticsInputStatusService {
getStatisticsInputStatusList(param: StatsInputStatusListRequestParams): Promise<StatsInputStatusListApiResponse>;
}
@@ -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
>;
@@ -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<MediationPumpStationItem[]> & {
message: string;
};
export interface MediationPumpStationService {
getMediationPumpStationList(params: {
yr?: string; // 조회 연도
bsnmCd?: string; // 사업소 코드
sido?: string; // 시도
gugun?: string; // 구군
bscFctNm?: string; // 펌프장명
}): Promise<MediationPumpStationApiResponse>;
}
@@ -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<SewerPipeMtcResponseData>;
export interface SewagePipeMtcService {
getSewerPipeMtcYearlyStatus(params: SewerPipeMtcRequestParams): Promise<SewerPipeMtcApiResponse>;
}
/**
* 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<SewerPipeDrdgResponseData>;
@@ -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<SewagePipeReviewrRegionalItem[]> & {
message: string;
};
export interface SewagePipeReviewService {
getSewagePipeReviewList(params: {
yr?: string; // 조회 연도
bsnmCd?: string; // 사업장 코드
sido?: string; // 시도
gugun?: string; // 구군
}): Promise<SewagePipeReviewRegionalApiResponse>;
getSewagePipeReviewRegionalList(params: SewagePipeReviewRequestParams): Promise<SewagePipeReviewRegionalApiResponse>;
}
/**
* 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<SewagePipeReviewFacilityItem[]> & {
export type SewagePipeFacilityApiResponse = ApiResponse<SewagePipeFacilityResponseData> & {
message: string;
};
export interface SewagePipeFacilityService {
getSewagePipeFacilityList(params: SewagePipeReviewRequestParams): Promise<SewagePipeFacilityApiResponse>;
}
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<SewerPipeTypeResponseData>;
export interface SewagePipeTypeService {
getSewagePipeTypeList(params: {
yr?: string; // 조회 연도
bsnmCd?: string; // 사업장 코드
sido?: string; // 시도
gugun?: string; // 구군
}): Promise<SewagePipeFacilityApiResponse>;
}): Promise<SewerPipeTypeApiResponse>;
}
/**
* 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<SewerPipeDiamResponseData>;
export interface SewagePipeDiamService {
getSewagePipeDiamList(params: {
yr?: string; // 조회 연도
bsnmCd?: string; // 사업장 코드
sido?: string; // 시도
gugun?: string; // 구군
}): Promise<SewerPipeDiamApiResponse>;
}
@@ -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<string, VerificationResultItem[]>;
/** 하수도 보급률 목록 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<SewerageSupplyRateResponseData> & {
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<SewerageSupplyRateSaveData>;
// 하수도 통계 입력 기간 조회
export interface CheckInputPeriodResponse {
status: string;
message: string;
data: boolean;
}
export interface StatisticsInputSewSupplyRateService {
getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise<SewerageSupplyRateApiResponse>;
checkInputPeriod(params: string): Promise<CheckInputPeriodResponse>;
saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse>;
submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse>;
}
@@ -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<SewerageSupplyRate
message: string;
};
export interface SewerageSupplyRateReviewRequestParams {
sido?: string;
gugun?: string;
yr?: string;
bsnmCd?: string;
}
export interface SewerageSupplyRateReviewService {
getSewerageSupplyRateReviewList(params: SewerageSupplyRateReviewRequestParams): Promise<SewerageSupplyRateReviewApiResponse>;
}
@@ -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",
],
};
@@ -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<StAnlsMstResponse>();
export const verificationHistoryColumns = [
columnHelper.accessor("anlsBgngDt", {
header: "검증 실행 일시",
cell: (info) => (
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
),
}),
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 (
<div className="flex min-w-[8rem] items-center gap-2">
<Progress
value={anlsPrgrsRt ?? 0}
className="h-2 flex-1"
/>
<span className="text-xs text-muted-foreground">
{anlsPrgrsRt ?? 0}%
</span>
</div>
);
}
return anlsSttsNm;
},
},
),
columnHelper.accessor("anlsEndDt", {
header: "종료 일시",
cell: (info) => (
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
),
}),
columnHelper.accessor("rgtrNm", {
header: "검증자",
cell: (info) => (
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
),
}),
columnHelper.accessor("nmlDataNocs", {
header: "정상건수",
cell: (info) => (
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
),
}),
columnHelper.accessor("exclDataNocs", {
header: "제외건수",
cell: (info) => (
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
),
}),
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 (
<button
type="button"
className="block w-full cursor-pointer bg-red-50 py-4 text-destructive underline"
onClick={(e) => {
e.stopPropagation();
onOutlierCellClick(row);
}}
>
{value}
</button>
);
}
return value;
},
}),
];
@@ -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;
}
@@ -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<string, string | number>[] {
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<string, string | number>;
});
}
/** 검증 이력 기본 목데이터 (10건) */
export const verificationHistoryMockData = createVerificationHistoryMock(10);
@@ -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<VerificationTargetManagementDetailApiResponse>;
getVerificationTargetCodes(): Promise<VerificationTargetCodeResponse[]>;
saveVerificationTargetManagement(
request: VerificationTargetManagementSaveRequest,
): Promise<VerificationTargetManagementSaveApiResponse>;
@@ -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<string, React.ComponentType>());
@@ -26,6 +28,10 @@ const StatisticsPage = () => {
});
}, [tabs]);
useEffect(() => {
fetchAdminInfo();
}, [fetchAdminInfo]);
// 탭이 없으면 Dashboard 표시
if (tabs.length === 0) {
return (
+6 -1
View File
@@ -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,
});
+6
View File
@@ -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",
},
};
+31 -23
View File
@@ -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<T> {
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[];
}
+2 -2
View File
@@ -22,7 +22,7 @@ const TabBar = () => {
<Tabs
value={activeTabId || undefined}
onValueChange={setActiveTab}
className="w-full"
className="w-full border-b border-border"
variant="outline"
>
<TabsList className="w-full !justify-start !p-4 !pb-0">
@@ -30,7 +30,7 @@ const TabBar = () => {
<TabsTrigger
key={tab.id}
value={tab.id}
className="!flex !w-[140px] !items-center !justify-start !gap-2 !px-4"
className="!flex !w-[140px] !items-center !justify-start !gap-2 !px-4 [&::after]:hidden"
title={tab.label}
>
<span className="flex-1 truncate text-xl leading-none">
+29
View File
@@ -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<void>;
}
const useAdminStoreHook = create<AdminStore>((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();
+38
View File
@@ -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",
},
},
];
+25 -22
View File
@@ -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"
@@ -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(
@@ -10,7 +10,10 @@ import { Button } from "../button";
import { Calendar } from "../calendar";
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
type DateRangePickerProps = React.HTMLAttributes<HTMLDivElement> &
type DateRangePickerProps = Omit<
React.HTMLAttributes<HTMLDivElement>,
"defaultValue"
> &
React.ComponentProps<typeof DayPicker> & {
value?: DateRange;
defaultValue?: DateRange;
+198 -25
View File
@@ -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: {}