[st] - 검토단 작업 완료 및 입력6-1, 10-1 운영데이터업데이트 기능 추가, 텝 영역 우클릭 후 텝 닫기 기능 추가
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly {
|
||||
@apply !bg-[#f0f0f0] !text-[#666];
|
||||
}
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-rowtype-curr {
|
||||
@apply !bg-white !text-[#333];
|
||||
}
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-verify-error {
|
||||
@apply !bg-[#ffe6e6] !text-[#c00];
|
||||
}
|
||||
@@ -153,6 +156,15 @@
|
||||
.rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-rowtype-other {
|
||||
@apply !bg-[#fff7bf];
|
||||
}
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-rowtype-curr,
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-rowtype-curr {
|
||||
@apply !bg-white !text-[#333];
|
||||
}
|
||||
.rg-grid .rg-fixed-body .rg-table .rg-data-row td.rg-cell-rowtype-curr,
|
||||
.rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-rowtype-curr,
|
||||
.rg-grid .rg-fixed-body .rg-data-cell.rg-cell-readonly.rg-cell-rowtype-curr {
|
||||
@apply !bg-white !text-[#333];
|
||||
}
|
||||
|
||||
/*
|
||||
* 검증 오류 셀(rg-cell-verify-error / -imr) 최우선 적용.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
import { useEffect, useLayoutEffect, useRef, type MutableRefObject } from "react";
|
||||
|
||||
import JSZip from "jszip";
|
||||
import { ValueType } from "realgrid";
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
CellStyleCallbackParams,
|
||||
CellStyleCallbackResult,
|
||||
CellTooltipCallback,
|
||||
ColumnConfig,
|
||||
ColumnLayoutItem,
|
||||
DataGridProps,
|
||||
FooterItemWithCallback,
|
||||
@@ -48,6 +49,63 @@ function mergeCellStyleCallbackResult(
|
||||
};
|
||||
}
|
||||
|
||||
type GridColumnWithStyleCallback = {
|
||||
styleCallback?: (
|
||||
grid: unknown,
|
||||
dataCell: {
|
||||
itemIndex?: number;
|
||||
index?: { itemIndex?: number };
|
||||
dataField?: string;
|
||||
fieldName?: string;
|
||||
},
|
||||
) => CellStyleCallbackResult;
|
||||
};
|
||||
|
||||
function applyCellStyleCallbacksToColumns(
|
||||
gridView: unknown,
|
||||
columns: ColumnConfig[],
|
||||
rowDataRef: MutableRefObject<Record<string, string | number>[]>,
|
||||
cellStyleCallbackRef: MutableRefObject<CellStyleCallback | undefined>,
|
||||
) {
|
||||
if (!cellStyleCallbackRef.current || !gridView) return;
|
||||
|
||||
const gv = gridView as {
|
||||
columnByName?: (name: string) => GridColumnWithStyleCallback | null | undefined;
|
||||
};
|
||||
if (typeof gv.columnByName !== "function") return;
|
||||
|
||||
columns.forEach((column) => {
|
||||
const baseStyleName = column.styleName;
|
||||
let gridCol: GridColumnWithStyleCallback | null | undefined;
|
||||
try {
|
||||
// columnByName은 GridView 인스턴스 메서드 — this 바인딩을 위해 gv에서 직접 호출
|
||||
gridCol = gv.columnByName!(column.name);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!gridCol) return;
|
||||
|
||||
gridCol.styleCallback = (grid, 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 mergeCellStyleCallbackResult(baseStyleName, undefined);
|
||||
}
|
||||
const row = data[itemIndex] as Record<string, string | number>;
|
||||
const extra = cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
});
|
||||
return mergeCellStyleCallbackResult(baseStyleName, extra);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// RealGrid 타입 확장
|
||||
declare module "realgrid" {
|
||||
interface GridView {
|
||||
@@ -725,9 +783,16 @@ const DataGrid = ({
|
||||
}, [highlightedRowId, rowData, idFieldName]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!cellStyleCallback || !gridRef.current?.gridView) return;
|
||||
gridRef.current.gridView.refresh();
|
||||
}, [cellStyleCallback, rowData]);
|
||||
const gv = gridRef.current?.gridView;
|
||||
if (!gv || !cellStyleCallback) return;
|
||||
applyCellStyleCallbacksToColumns(
|
||||
gv,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
(gv as { refresh?: () => void }).refresh?.();
|
||||
}, [cellStyleCallback, columns, rowData]);
|
||||
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const activeTabId = useTabStore((state) => state.activeTabId);
|
||||
@@ -1049,6 +1114,10 @@ const DataGrid = ({
|
||||
|
||||
const gridView = gridRef.current.gridView;
|
||||
const dataProvider = gridRef.current.dataProvider;
|
||||
const footerTimerIds: ReturnType<typeof setTimeout>[] = [];
|
||||
const scheduleFooterTask = (task: () => void, delayMs: number) => {
|
||||
footerTimerIds.push(setTimeout(task, delayMs));
|
||||
};
|
||||
applyDefaultGridEditOptions(gridView as GridViewWithEditOptions);
|
||||
|
||||
const columnsFieldsSig = columns.map((c) => c.fieldName).join("\u0001");
|
||||
@@ -1189,8 +1258,10 @@ const DataGrid = ({
|
||||
});
|
||||
|
||||
const applyFooterSettings = (
|
||||
targetGridView: typeof gridView,
|
||||
targetGridView: typeof gridView | null | undefined,
|
||||
) => {
|
||||
if (!targetGridView?.setOptions) return;
|
||||
|
||||
const hasFooter = columns.some((col) => col.footer);
|
||||
if (!hasFooter) return;
|
||||
|
||||
@@ -1308,7 +1379,7 @@ const DataGrid = ({
|
||||
};
|
||||
|
||||
// setColumnLayout은 그리드가 완전히 렌더링된 후 호출해야 함
|
||||
setTimeout(() => {
|
||||
scheduleFooterTask(() => {
|
||||
if (gridRef.current?.gridView && columnLayout) {
|
||||
const safeLayout = sanitizeColumnLayout(columnLayout as unknown[]);
|
||||
if (safeLayout.length === 0) return;
|
||||
@@ -1327,10 +1398,9 @@ const DataGrid = ({
|
||||
|
||||
// footer 설정 (배열 또는 단일 객체 모두 처리)
|
||||
// setFooters 호출 후 footer 배열 설정
|
||||
setTimeout(() => {
|
||||
scheduleFooterTask(() => {
|
||||
const currentGridView = gridRef.current?.gridView;
|
||||
if (!currentGridView || !currentGridView.columnByName) {
|
||||
console.warn("GridView not available for footer setup");
|
||||
if (!currentGridView?.columnByName) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1385,19 +1455,28 @@ const DataGrid = ({
|
||||
col,
|
||||
),
|
||||
);
|
||||
applyCellStyleCallbacksToColumns(
|
||||
currentGridView,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
applyFooterSettings(currentGridView);
|
||||
if (hasFooterUserSpans) {
|
||||
currentGridView.setColumnLayout?.(columnLayout);
|
||||
}
|
||||
|
||||
// setColumns 호출 후 footer 배열의 valueCallback을 다시 설정
|
||||
setTimeout(() => {
|
||||
scheduleFooterTask(() => {
|
||||
const liveGridView = gridRef.current?.gridView;
|
||||
if (!liveGridView?.columnByName) return;
|
||||
|
||||
columns.forEach((col) => {
|
||||
if (!col.footer || !Array.isArray(col.footer))
|
||||
return;
|
||||
|
||||
try {
|
||||
const column = currentGridView.columnByName(
|
||||
const column = liveGridView.columnByName(
|
||||
col.name,
|
||||
);
|
||||
if (!column) return;
|
||||
@@ -1429,11 +1508,17 @@ const DataGrid = ({
|
||||
);
|
||||
}
|
||||
});
|
||||
applyFooterSettings(currentGridView);
|
||||
applyCellStyleCallbacksToColumns(
|
||||
liveGridView,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
applyFooterSettings(liveGridView);
|
||||
if (hasFooterUserSpans) {
|
||||
currentGridView.setColumnLayout?.(columnLayout);
|
||||
liveGridView.setColumnLayout?.(columnLayout);
|
||||
}
|
||||
currentGridView.refresh?.();
|
||||
liveGridView.refresh?.();
|
||||
}, 50);
|
||||
} catch (e) {
|
||||
console.warn("Failed to set columns with footer array:", e);
|
||||
@@ -1495,6 +1580,7 @@ const DataGrid = ({
|
||||
}
|
||||
|
||||
return () => {
|
||||
footerTimerIds.forEach(clearTimeout);
|
||||
if (getGridDataRef) getGridDataRef.current = null;
|
||||
};
|
||||
}, [
|
||||
@@ -1939,19 +2025,9 @@ const DataGrid = ({
|
||||
};
|
||||
}
|
||||
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;
|
||||
},
|
||||
) => CellStyleCallbackResult;
|
||||
renderer?: { showTooltip?: boolean };
|
||||
} | null;
|
||||
if (gridCol) {
|
||||
@@ -1990,43 +2066,14 @@ const DataGrid = ({
|
||||
{ showTooltip: true },
|
||||
);
|
||||
}
|
||||
gridCol.styleCallback = (grid, 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 mergeCellStyleCallbackResult(
|
||||
baseStyleName,
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
const row = data[itemIndex] as Record<
|
||||
string,
|
||||
string | number
|
||||
>;
|
||||
const extra =
|
||||
cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
});
|
||||
return mergeCellStyleCallbackResult(
|
||||
baseStyleName,
|
||||
extra,
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
applyCellStyleCallbacksToColumns(
|
||||
gv,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
gv.refresh();
|
||||
}, 0);
|
||||
}}
|
||||
|
||||
@@ -234,6 +234,7 @@ export function VerificationResultHistoryDialog({
|
||||
};
|
||||
const checkedVrfcByTabRef = useRef(checkedVrfcByTab);
|
||||
checkedVrfcByTabRef.current = checkedVrfcByTab;
|
||||
const loadedTabKeysRef = useRef<Set<VerificationHistoryTabKey>>(new Set());
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setActiveTab("NV");
|
||||
@@ -243,6 +244,7 @@ export function VerificationResultHistoryDialog({
|
||||
setErrorMessage(null);
|
||||
setResetSubmitting(false);
|
||||
setCheckedVrfcByTab(createEmptyCheckedVrfcByTab());
|
||||
loadedTabKeysRef.current.clear();
|
||||
}, []);
|
||||
|
||||
const fetchVerificationResultPage = useCallback(
|
||||
@@ -279,10 +281,10 @@ export function VerificationResultHistoryDialog({
|
||||
);
|
||||
|
||||
const loadTabPage = useCallback(
|
||||
async (tab: VerificationHistoryTabKey, pageIndex: number) => {
|
||||
async (tab: VerificationHistoryTabKey, pageIndex: number): Promise<boolean> => {
|
||||
if (!stableQueryContext) {
|
||||
setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setLoadingByTab((prev) => ({ ...prev, [tab]: true }));
|
||||
@@ -296,10 +298,12 @@ export function VerificationResultHistoryDialog({
|
||||
? Math.max(0, Number(apiCurrentPage) - 1)
|
||||
: pageIndex;
|
||||
applyPageResponse(tab, resolvedPageIndex, list, totalPages);
|
||||
return true;
|
||||
} catch {
|
||||
const tabLabel =
|
||||
verificationHistoryTabs.find((t) => t.value === tab)?.label ?? tab;
|
||||
setErrorMessage(`${tabLabel} 조회 중 오류가 발생했습니다.`);
|
||||
return false;
|
||||
} finally {
|
||||
setLoadingByTab((prev) => ({ ...prev, [tab]: false }));
|
||||
}
|
||||
@@ -314,40 +318,27 @@ export function VerificationResultHistoryDialog({
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setTabPages(initialTabPageState());
|
||||
setLoadingByTab({ NV: true, IMR: true, HB: true });
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
verificationHistoryTabs.map((tab) =>
|
||||
fetchVerificationResultPage(tab.value, 0),
|
||||
),
|
||||
);
|
||||
setEntriesByTab({
|
||||
NV: results[0]?.data?.verifyResultList ?? [],
|
||||
IMR: results[1]?.data?.verifyResultList ?? [],
|
||||
HB: results[2]?.data?.verifyResultList ?? [],
|
||||
});
|
||||
setTabPages({
|
||||
NV: {
|
||||
pageIndex: 0,
|
||||
pageCount: Math.max(results[0]?.data?.pageInfo?.totalPages ?? 1, 1),
|
||||
},
|
||||
IMR: {
|
||||
pageIndex: 0,
|
||||
pageCount: Math.max(results[1]?.data?.pageInfo?.totalPages ?? 1, 1),
|
||||
},
|
||||
HB: {
|
||||
pageIndex: 0,
|
||||
pageCount: Math.max(results[2]?.data?.pageInfo?.totalPages ?? 1, 1),
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
setEntriesByTab({ NV: [], IMR: [], HB: [] });
|
||||
setTabPages(initialTabPageState());
|
||||
setErrorMessage("검증 결과 내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setLoadingByTab({ NV: false, IMR: false, HB: false });
|
||||
setEntriesByTab({ NV: [], IMR: [], HB: [] });
|
||||
loadedTabKeysRef.current.clear();
|
||||
const loaded = await loadTabPage("NV", 0);
|
||||
if (loaded) {
|
||||
loadedTabKeysRef.current.add("NV");
|
||||
}
|
||||
}, [fetchVerificationResultPage, stableQueryContext]);
|
||||
}, [loadTabPage, stableQueryContext]);
|
||||
|
||||
const handleActiveTabChange = useCallback(
|
||||
(tab: VerificationHistoryTabKey) => {
|
||||
setActiveTab(tab);
|
||||
if (loadedTabKeysRef.current.has(tab)) return;
|
||||
void (async () => {
|
||||
const loaded = await loadTabPage(tab, 0);
|
||||
if (loaded) {
|
||||
loadedTabKeysRef.current.add(tab);
|
||||
}
|
||||
})();
|
||||
},
|
||||
[loadTabPage],
|
||||
);
|
||||
|
||||
const handleTabPageChange = useCallback(
|
||||
(tab: VerificationHistoryTabKey, pageIndex: number) => {
|
||||
@@ -577,7 +568,7 @@ export function VerificationResultHistoryDialog({
|
||||
)}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(value) => setActiveTab(value as VerificationHistoryTabKey)}
|
||||
onValueChange={(value) => handleActiveTabChange(value as VerificationHistoryTabKey)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
|
||||
@@ -582,11 +582,6 @@ const PublicSewageFacilityInp = () => {
|
||||
})();
|
||||
};
|
||||
|
||||
const updateOperateDataButtonFunc = () => {
|
||||
console.log('updatedOperateData');
|
||||
// 운영 데이터 업데이트 로직
|
||||
};
|
||||
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [activeTab, setActiveTab] = useState<string>(
|
||||
PublicSewageFactInpColumnList[0]?.tabKey ?? 'ST00060001',
|
||||
@@ -766,6 +761,52 @@ const PublicSewageFacilityInp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateOperateDataButtonFunc = () => {
|
||||
void (async () => {
|
||||
if (!lastSearchedValues) {
|
||||
alert('먼저 조회를 진행해주세요.');
|
||||
return;
|
||||
}
|
||||
if (!(await confirm('운영 데이터를 업데이트 하시겠습니까?'))) return;
|
||||
|
||||
try {
|
||||
showGlobalProgress({ label: '운영 데이터 업데이트 중...', progress: 0 });
|
||||
const userInfo = await getUserInfo();
|
||||
const request = {
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
};
|
||||
|
||||
const response =
|
||||
await statisticsInputPublicSewageFacilityApiService.updatePublicSewageFacilityOperationData(
|
||||
request,
|
||||
);
|
||||
if (response.status !== 'success') {
|
||||
alert(response.message || '운영 데이터 업데이트 중 오류가 발생했습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
setGlobalProgress(50);
|
||||
const param: PublicSewageFacilityRequestParams = {
|
||||
statsYr: request.statsYr,
|
||||
ctpvNm: request.ctpvNm,
|
||||
sggNm: request.sggNm,
|
||||
sewerBzmnCd: request.sewerBzmnCd,
|
||||
};
|
||||
await loadPublicSewageFacilityActiveTabData('ST00060001', param, lastSearchedValues);
|
||||
setGlobalProgress(100);
|
||||
alert('운영 데이터가 업데이트되었습니다.');
|
||||
} catch {
|
||||
alert('운영 데이터 업데이트 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
hideGlobalProgress();
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const runMainFacilityMappingSave = async (
|
||||
draft: MainFacilityMappingSaveDraft,
|
||||
dialogInput: MainFacilityMappingSaveDialogValues | null,
|
||||
|
||||
@@ -660,7 +660,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
Map<string, SewerageStatusInfo | null>
|
||||
>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
||||
|
||||
@@ -50,7 +50,7 @@ import SearchForm from "@/components/module/SearchForm";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { PipeDredgingStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/PipeDredgingStatusInpColumn";
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type RefObject,
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
@@ -25,7 +24,7 @@ import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButt
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid, { applyDropDownColumnToGrid } from "@/components/module/DataGrid";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -200,20 +199,6 @@ function createEmptyRainfallPumpStationRow(
|
||||
return row;
|
||||
}
|
||||
|
||||
function applySggNmDropDownToReservoirGrid(
|
||||
gridRef: RefObject<RealGridReact | null>,
|
||||
labels: string[],
|
||||
) {
|
||||
if (labels.length === 0) return;
|
||||
applyDropDownColumnToGrid(
|
||||
gridRef.current?.gridView as {
|
||||
setColumnProperty?: (col: string, key: string, value: unknown) => void;
|
||||
},
|
||||
"sggNm",
|
||||
labels,
|
||||
);
|
||||
}
|
||||
|
||||
function parseSggListFromResponse(res: ApiResponse<Sgg[]>): Sgg[] {
|
||||
if (res.status !== "success" || res.data == null) return [];
|
||||
return Array.isArray(res.data) ? res.data : [];
|
||||
@@ -283,6 +268,16 @@ function toGridNumber(value: string | number | null | undefined): number {
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
/** 펌프 구경(mm) — 콤마 제거 후 숫자 변환, 소수점 버림 */
|
||||
function toPumpDmtrExpln(value: string | number | null | undefined): number {
|
||||
const raw = String(value ?? "").trim();
|
||||
if (raw === "") return 0;
|
||||
const normalized = raw.replace(/,/g, "");
|
||||
const num = Number(normalized);
|
||||
if (!Number.isFinite(num)) return 0;
|
||||
return Math.floor(num);
|
||||
}
|
||||
|
||||
/** 시군구명(`sggNm`) 기준 `admdstCd`(bsnmCd)·`admdstNm`(시군구명) */
|
||||
function resolveSewageReservoirAdmdst(
|
||||
sggNm: string,
|
||||
@@ -322,15 +317,15 @@ function buildSewageReservoirSaveItem(
|
||||
wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea),
|
||||
impdFcltCpct: toGridNumber(row.impdFcltCpct),
|
||||
grssPpqty: toGridNumber(row.grssPpqty),
|
||||
pumpFcltN1DmtrExpln: String(row.pumpFcltN1DmtrExpln ?? ""),
|
||||
pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln),
|
||||
pumpFcltN1PerPpqty: toGridNumber(row.pumpFcltN1PerPpqty),
|
||||
pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt),
|
||||
pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt),
|
||||
pumpFcltN2DmtrExpln: String(row.pumpFcltN2DmtrExpln ?? ""),
|
||||
pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln),
|
||||
pumpFcltN2PerPpqty: toGridNumber(row.pumpFcltN2PerPpqty),
|
||||
pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt),
|
||||
pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt),
|
||||
pumpFcltN3DmtrExpln: String(row.pumpFcltN3DmtrExpln ?? ""),
|
||||
pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln),
|
||||
pumpFcltN3PerPpqty: toGridNumber(row.pumpFcltN3PerPpqty),
|
||||
pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt),
|
||||
pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt),
|
||||
@@ -375,15 +370,15 @@ function buildRainfallPumpStationSaveItem(
|
||||
rsvrImpndQnt: toGridNumber(row.rsvrImpndQnt),
|
||||
abstnRgnImpdQnt: toGridNumber(row.abstnRgnImpdQnt),
|
||||
grssPpqty: toGridNumber(row.grssPpqty),
|
||||
pumpFcltN1DmtrExpln: String(row.pumpFcltN1DmtrExpln ?? ""),
|
||||
pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln),
|
||||
pumpFcltN1PerPpqty: toGridNumber(row.pumpFcltN1PerPpqty),
|
||||
pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt),
|
||||
pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt),
|
||||
pumpFcltN2DmtrExpln: String(row.pumpFcltN2DmtrExpln ?? ""),
|
||||
pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln),
|
||||
pumpFcltN2PerPpqty: toGridNumber(row.pumpFcltN2PerPpqty),
|
||||
pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt),
|
||||
pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt),
|
||||
pumpFcltN3DmtrExpln: String(row.pumpFcltN3DmtrExpln ?? ""),
|
||||
pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln),
|
||||
pumpFcltN3PerPpqty: toGridNumber(row.pumpFcltN3PerPpqty),
|
||||
pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt),
|
||||
pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt),
|
||||
|
||||
@@ -850,8 +850,8 @@ const WaterReuseInp = () => {
|
||||
(typeof gridView.getCheckedItems === "function"
|
||||
? gridView.getCheckedItems()
|
||||
: typeof gridView.getCheckedRows === "function"
|
||||
? gridView.getCheckedRows()
|
||||
: []) ?? [];
|
||||
? gridView.getCheckedRows()
|
||||
: []) ?? [];
|
||||
|
||||
return checked.filter(
|
||||
(idx) => Number.isInteger(idx) && idx >= 0 && idx < rowCount,
|
||||
@@ -1166,8 +1166,8 @@ const WaterReuseInp = () => {
|
||||
const submitList = noApplicable
|
||||
? []
|
||||
: currentData.map((row) =>
|
||||
buildRecycledWaterStatusSaveItem(row, list),
|
||||
);
|
||||
buildRecycledWaterStatusSaveItem(row, list),
|
||||
);
|
||||
const deleteList = (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRow(tabKey, row))
|
||||
.map((row) => buildRecycledWaterStatusSaveItem(row, list));
|
||||
@@ -1186,8 +1186,8 @@ const WaterReuseInp = () => {
|
||||
const submitList = noApplicable
|
||||
? []
|
||||
: currentData.map((row) =>
|
||||
buildRainFallUtilFactStatusSaveItem(row, list),
|
||||
);
|
||||
buildRainFallUtilFactStatusSaveItem(row, list),
|
||||
);
|
||||
const deleteList = (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRow(tabKey, row))
|
||||
.map((row) => buildRainFallUtilFactStatusSaveItem(row, list));
|
||||
@@ -1206,8 +1206,8 @@ const WaterReuseInp = () => {
|
||||
const submitList = noApplicable
|
||||
? []
|
||||
: currentData.map((row) =>
|
||||
buildTreatWaterReuseDesignConstStatusSaveItem(row, list),
|
||||
);
|
||||
buildTreatWaterReuseDesignConstStatusSaveItem(row, list),
|
||||
);
|
||||
const deleteList = (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRow(tabKey, row))
|
||||
.map((row) =>
|
||||
@@ -1231,8 +1231,8 @@ const WaterReuseInp = () => {
|
||||
const submitList = noApplicable
|
||||
? []
|
||||
: currentData.map((row) =>
|
||||
buildTreatWaterReuseBusinessApprStatusSaveItem(row, list),
|
||||
);
|
||||
buildTreatWaterReuseBusinessApprStatusSaveItem(row, list),
|
||||
);
|
||||
const deleteList = (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRow(tabKey, row))
|
||||
.map((row) =>
|
||||
@@ -1417,8 +1417,8 @@ const WaterReuseInp = () => {
|
||||
const saveList = noApplicable
|
||||
? []
|
||||
: currentData.map((row) =>
|
||||
buildRecycledWaterStatusSaveItem(row, list),
|
||||
);
|
||||
buildRecycledWaterStatusSaveItem(row, list),
|
||||
);
|
||||
const deleteList = (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRow(tabKey, row))
|
||||
.map((row) => buildRecycledWaterStatusSaveItem(row, list));
|
||||
@@ -1477,8 +1477,8 @@ const WaterReuseInp = () => {
|
||||
const saveList = noApplicable
|
||||
? []
|
||||
: currentData.map((row) =>
|
||||
buildRainFallUtilFactStatusSaveItem(row, list),
|
||||
);
|
||||
buildRainFallUtilFactStatusSaveItem(row, list),
|
||||
);
|
||||
const deleteList = (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRow(tabKey, row))
|
||||
.map((row) => buildRainFallUtilFactStatusSaveItem(row, list));
|
||||
@@ -1538,8 +1538,8 @@ const WaterReuseInp = () => {
|
||||
const saveList = noApplicable
|
||||
? []
|
||||
: currentData.map((row) =>
|
||||
buildTreatWaterReuseDesignConstStatusSaveItem(row, list),
|
||||
);
|
||||
buildTreatWaterReuseDesignConstStatusSaveItem(row, list),
|
||||
);
|
||||
const deleteList = (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRow(tabKey, row))
|
||||
.map((row) =>
|
||||
@@ -1603,8 +1603,8 @@ const WaterReuseInp = () => {
|
||||
const saveList = noApplicable
|
||||
? []
|
||||
: currentData.map((row) =>
|
||||
buildTreatWaterReuseBusinessApprStatusSaveItem(row, list),
|
||||
);
|
||||
buildTreatWaterReuseBusinessApprStatusSaveItem(row, list),
|
||||
);
|
||||
const deleteList = (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRow(tabKey, row))
|
||||
.map((row) =>
|
||||
@@ -1652,7 +1652,56 @@ const WaterReuseInp = () => {
|
||||
};
|
||||
|
||||
const updateOperateDataButtonFunc = () => {
|
||||
alert("운영 데이터 업데이트 API 연동 준비 중입니다.");
|
||||
void (async () => {
|
||||
if (!lastSearchedValues) {
|
||||
alert("먼저 조회를 진행해주세요.");
|
||||
return;
|
||||
}
|
||||
if (!(await confirm("운영 데이터를 업데이트 하시겠습니까?"))) return;
|
||||
|
||||
try {
|
||||
showGlobalProgress({ label: "운영 데이터 업데이트 중...", progress: 0 });
|
||||
const userInfo = await getUserInfo();
|
||||
const request = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
};
|
||||
|
||||
const response =
|
||||
await statisticsInputWaterReuseApi.updateWaterReuseOperationData(request);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "운영 데이터 업데이트 중 오류가 발생했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setGlobalProgress(50);
|
||||
const param: WaterReuseRequestParams = {
|
||||
statsYr: request.statsYr,
|
||||
ctpvNm: request.ctpvNm,
|
||||
sggNm: request.sggNm,
|
||||
sewerBzmnCd: request.sewerBzmnCd,
|
||||
};
|
||||
await loadActiveTabData(
|
||||
PublicSewageTreatWaterReuseInpColumn.tabKey,
|
||||
param,
|
||||
lastSearchedValues,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
setGlobalProgress(100);
|
||||
alert("운영 데이터가 업데이트되었습니다.");
|
||||
} catch {
|
||||
alert("운영 데이터 업데이트 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
hideGlobalProgress();
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const addArticleFunc = async () => {
|
||||
@@ -1716,10 +1765,10 @@ const WaterReuseInp = () => {
|
||||
checkedIndexes.length > 0
|
||||
? new Set(checkedIndexes)
|
||||
: new Set(
|
||||
currentRows
|
||||
.map((row, idx) => (isCheckedRow(row) ? idx : -1))
|
||||
.filter((idx) => idx >= 0),
|
||||
);
|
||||
currentRows
|
||||
.map((row, idx) => (isCheckedRow(row) ? idx : -1))
|
||||
.filter((idx) => idx >= 0),
|
||||
);
|
||||
|
||||
const removedRows = currentRows.filter((_, idx) => checkedIndexSet.has(idx));
|
||||
const nextRows = currentRows.filter((_, idx) => !checkedIndexSet.has(idx));
|
||||
@@ -1951,8 +2000,8 @@ const WaterReuseInp = () => {
|
||||
e.vrfcCd === "IMR"
|
||||
? "IMR검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "HB검증 - ";
|
||||
? "일반검증 - "
|
||||
: "HB검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
@@ -1,38 +1,315 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { TabDataGrid } from "@rsone/components";
|
||||
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 { LoadingWithAnimation } from "@common/components";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type {
|
||||
ExcretaTreatmentFacilityReviewItem,
|
||||
} from "@/entities/statistics/model/statistics-excreta-treatment-facility-review-interface";
|
||||
import {
|
||||
extractExcretaTreatmentFacilityReviewList,
|
||||
extractExcretaTreatmentFacilityReviewStatsStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-excreta-treatment-facility-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewOpinionSaveRequest,
|
||||
ReviewerInfoRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { ExcretaTreatmentFacilityInfoList } from "@/components/util/column/review/ExcretaTreatmentFacilityRvwColumn";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 하수도 통계 관리 07.분뇨처리 시설
|
||||
const excretaTreatmentFacilityTab = ExcretaTreatmentFacilityInfoList[0];
|
||||
|
||||
// 하수도 통계 검토 07. 분뇨처리시설
|
||||
const ExcretaTreatmentFacilityRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [statsStatusInfo, setStatsStatusInfo] = useState<StatsStatusInfo>({});
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [reviewOpinionContext, setReviewOpinionContext] =
|
||||
useState<ReviewOpinionDialogContext | null>(null);
|
||||
const [latestReviewOpinion, setLatestReviewOpinion] = useState("");
|
||||
const [latestProcessHistory, setLatestProcessHistory] = useState("");
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const columnInfo = useMemo(() => {
|
||||
if (!excretaTreatmentFacilityTab) {
|
||||
return { columns: [], columnLayout: [], fixedColumns: 0, rowStartNum: 0 };
|
||||
}
|
||||
|
||||
const approveFunc = () => console.log('approveFunc');
|
||||
const saveFunc = () => console.log('submit');
|
||||
const reviewOpinionFunc = () => console.log('reviewOpinionFunc');
|
||||
const returnFunc = () => console.log('returnFunc');
|
||||
const { columns, columnLayout, fixedColumns, rowStartNum } = excretaTreatmentFacilityTab;
|
||||
return {
|
||||
columnLayout,
|
||||
fixedColumns,
|
||||
rowStartNum,
|
||||
columns: columns.map((col) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
})),
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [statsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (reviewOpinionContext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
if (!instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [reviewOpinionContext, userInfo?.instSeCd]);
|
||||
|
||||
const handleReset = () => {
|
||||
setRowData([]);
|
||||
setStatsStatusInfo({});
|
||||
setReviewOpinionContext(null);
|
||||
setLatestReviewOpinion("");
|
||||
setLatestProcessHistory("");
|
||||
setLastSearchValues(null);
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
setLastSearchValues(values);
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response =
|
||||
await statisticsManagementApi.getExcretaTreatmentFacilityReviewList({
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
statsYr: values.year,
|
||||
swtrmtFcltNm: values.fctName,
|
||||
});
|
||||
|
||||
if (response.status === "success" && response.data) {
|
||||
const rawList = extractExcretaTreatmentFacilityReviewList(response.data);
|
||||
const statsInfo = extractExcretaTreatmentFacilityReviewStatsStatusInfo(response.data);
|
||||
const convertedData: ExcretaTreatmentFacilityReviewItem[] = rawList.map((item) => ({
|
||||
...item,
|
||||
admdstNm: item.admdstNm ?? "",
|
||||
fcltPstnCn: item.fcltPstnCn ?? "",
|
||||
telNo: item.telNo ?? "",
|
||||
cmptnStrtYmd: item.cmptnStrtYmd ?? "",
|
||||
oprtngStrtYmd: item.oprtngStrtYmd ?? "",
|
||||
prcsMthdExpln: item.prcsMthdExpln ?? "",
|
||||
linkTmplntCn: item.linkTmplntCn ?? "",
|
||||
wtsysNm: item.wtsysNm ?? "",
|
||||
trbtrNm: item.trbtrNm ?? "",
|
||||
msirbCn: item.msirbCn ?? "",
|
||||
ssirbCn: item.ssirbCn ?? "",
|
||||
operMthdCdCn: item.operMthdCdCn ?? "",
|
||||
rmrk: item.rmrk ?? "",
|
||||
}));
|
||||
|
||||
setStatsStatusInfo(statsInfo);
|
||||
setReviewOpinionContext({
|
||||
statsYr: values.year ?? "",
|
||||
sewerBzmnCd: values.bsnmCd ?? "",
|
||||
statsCd: statsInfo.statsCd ?? "",
|
||||
});
|
||||
setLatestReviewOpinion(statsInfo.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(statsInfo.wrterOpnnCn ?? "");
|
||||
setRowData(toGridRows(convertedData as unknown as object[]));
|
||||
} else {
|
||||
setRowData([]);
|
||||
setStatsStatusInfo({});
|
||||
setReviewOpinionContext(null);
|
||||
setLatestReviewOpinion("");
|
||||
setLatestProcessHistory("");
|
||||
alert(response.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.wrterOpnnCn ?? "");
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setStatsStatusInfo((prev) => ({
|
||||
...prev,
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
}));
|
||||
setLatestReviewOpinion(param.splmntOpnnCn);
|
||||
setLatestProcessHistory(param.wrterOpnnCn);
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm fctName={true} />
|
||||
<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} />
|
||||
<div className="relative">
|
||||
<SearchForm fctName={true} onSubmit={handleSearch} onReset={handleReset} />
|
||||
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && <SubmitButton submitFunc={submitFunc} />}
|
||||
</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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
excelExportFileSuffix={excretaTreatmentFacilityTab?.tabLabel ?? "분뇨처리시설"}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
<TabDataGrid columnInfo={ExcretaTreatmentFacilityInfoList} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExcretaTreatmentFacilityRvw;
|
||||
export default ExcretaTreatmentFacilityRvw;
|
||||
|
||||
@@ -1,51 +1,428 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { TabDataGrid } from "@rsone/components";
|
||||
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 { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
extractPrivateSewageFacilityReviewList,
|
||||
extractPrivateSewageFacilityReviewStatsStatusInfo,
|
||||
mapPrivateSewageFacilityReviewRows,
|
||||
type PrivateSewageFacilityReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-private-sewage-facility-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { PrivateSewageFacilityInfoListRvw } from "@/components/util/column/review/PrivateSewageFacilityColumn";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 하수도 통계 관리 08.개인하수처리시설
|
||||
const firstTabKey = PrivateSewageFacilityInfoListRvw[0]?.tabKey ?? "ST00080001";
|
||||
|
||||
async function fetchPrivateSewageFacilityReviewTab(
|
||||
tabKey: string,
|
||||
searchParam: PrivateSewageFacilityReviewRequestParams,
|
||||
) {
|
||||
if (tabKey === "ST00080001") {
|
||||
return statisticsManagementApi.getPrivateSewageTreatFactStatusReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00080002") {
|
||||
return statisticsManagementApi.getSepticTankStatusReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00080003") {
|
||||
return statisticsManagementApi.getPrivateSewageDesigStatusReviewList(searchParam);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 하수도 통계 검토 08. 개인하수처리시설
|
||||
const PrivateSewageFacilityRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [activeTab, setActiveTab] = useState<string>(firstTabKey);
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
|
||||
Map<string, StatsStatusInfo>
|
||||
>(new Map());
|
||||
|
||||
const approveFunc = () => {
|
||||
console.log('approveFunc');
|
||||
// 승인 로직
|
||||
const searchParamRef = useRef<PrivateSewageFacilityReviewRequestParams | null>(null);
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoggedRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const activeStatsStatusInfo = useMemo(
|
||||
() => tabStatsStatusInfoMap.get(activeTab) ?? {},
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
return PrivateSewageFacilityInfoListRvw.map((tab: ColumnInfoList) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col: ColumnConfig) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||
})),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
return {
|
||||
statsYr: lastSearchValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchValues.bsnmCd ?? "",
|
||||
statsCd: activeStatsStatusInfo.statsCd ?? "",
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchValues.bsnmCd ?? "";
|
||||
const statsCd = activeStatsStatusInfo.statsCd ?? "";
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !statsCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues, userInfo?.instSeCd]);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [activeStatsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
setLastSearchValues(null);
|
||||
searchParamRef.current = null;
|
||||
searchDoneRef.current = false;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
};
|
||||
|
||||
const saveFunc = () => {
|
||||
console.log('submit');
|
||||
// 저장 로직
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLastSearchValues(values);
|
||||
|
||||
const searchParam: PrivateSewageFacilityReviewRequestParams = {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
};
|
||||
|
||||
searchParamRef.current = searchParam;
|
||||
searchDoneRef.current = true;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
|
||||
const response = await fetchPrivateSewageFacilityReviewTab(activeTab, searchParam);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractPrivateSewageFacilityReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, ssi);
|
||||
return next;
|
||||
});
|
||||
const listData = extractPrivateSewageFacilityReviewList(activeTab, response.data);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, toGridRows(mapPrivateSewageFacilityReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = () => {
|
||||
console.log('reviewOpinionFunc');
|
||||
// 검토의견 작성
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
setActiveTab(tabKey);
|
||||
|
||||
const activeTabData = rowDataMap.get(tabKey);
|
||||
const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0;
|
||||
|
||||
const canLazyLoad =
|
||||
searchParamRef.current &&
|
||||
!hasActiveTabData &&
|
||||
!lazyTabLoggedRef.current.has(tabKey) &&
|
||||
searchDoneRef.current;
|
||||
|
||||
if (!canLazyLoad) return;
|
||||
|
||||
const searchParam = searchParamRef.current;
|
||||
if (!searchParam) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await fetchPrivateSewageFacilityReviewTab(tabKey, searchParam);
|
||||
lazyTabLoggedRef.current.add(tabKey);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractPrivateSewageFacilityReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, ssi);
|
||||
return next;
|
||||
});
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const listData = extractPrivateSewageFacilityReviewList(tabKey, response.data);
|
||||
next.set(tabKey, toGridRows(mapPrivateSewageFacilityReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => {
|
||||
console.log('returnFunc');
|
||||
// 반려
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm fctName={true} />
|
||||
<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} />
|
||||
<div className="relative">
|
||||
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
||||
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={() => submitFunc()} />
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
<TabDataGrid columnInfo={PrivateSewageFacilityInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
import { PublicSewageFacilityInfoList } from "@/components/util/column/review/PublicSewageFacilityRvwColumn";
|
||||
|
||||
@@ -250,6 +250,7 @@ const PublicSewageFacilityRvw = () => {
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
|
||||
@@ -1,26 +1,52 @@
|
||||
import { useState, useMemo } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import {
|
||||
extractMediationPumpStationList,
|
||||
extractMediationPumpStationStatsStatusInfo,
|
||||
type MediationPumpStationItem,
|
||||
} from "@/entities/statistics/model/statistics-pumping-station-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewOpinionSaveRequest,
|
||||
ReviewerInfoRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
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 { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import type { MediationPumpStationItem } from "@/entities/statistics/model/statistics-pumping-station-review-interface";
|
||||
import { PumpingStationInfoList } from "@/components/util/column/review/PumpingStationRvwColumn";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
/**
|
||||
* 하수도 통계 검토 05.중계펌프장
|
||||
*/
|
||||
const PumpingStationRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [statsStatusInfo, setStatsStatusInfo] = useState<StatsStatusInfo>({});
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [reviewOpinionContext, setReviewOpinionContext] =
|
||||
useState<ReviewOpinionDialogContext | null>(null);
|
||||
const [latestReviewOpinion, setLatestReviewOpinion] = useState("");
|
||||
const [latestProcessHistory, setLatestProcessHistory] = useState("");
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const { columns, columnLayout, fixedColumns, rowStartNum } = PumpingStationInfoList;
|
||||
@@ -36,65 +62,193 @@ const PumpingStationRvw = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [statsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (reviewOpinionContext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
if (!instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [reviewOpinionContext, userInfo?.instSeCd]);
|
||||
|
||||
const handleReset = () => {
|
||||
setRowData([]);
|
||||
setStatsStatusInfo({});
|
||||
setReviewOpinionContext(null);
|
||||
setLatestReviewOpinion("");
|
||||
setLatestProcessHistory("");
|
||||
setLastSearchValues(null);
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === 'ALL') {
|
||||
alert('시도를 선택해주세요.');
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
setLastSearchValues(values);
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await statisticsManagementApi.getMediationPumpStationList({
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
statsYr: values.year,
|
||||
pumpstNm: values.fctName,
|
||||
});
|
||||
|
||||
if (response.status === 'success' && response.data) {
|
||||
const rawList = response.data as MediationPumpStationItem[];
|
||||
|
||||
const convertedData = rawList.map(item => ({
|
||||
if (response.status === "success" && response.data) {
|
||||
const rawList = extractMediationPumpStationList(response.data);
|
||||
const statsInfo = extractMediationPumpStationStatsStatusInfo(response.data);
|
||||
|
||||
const convertedData = rawList.map((item: MediationPumpStationItem) => ({
|
||||
...item,
|
||||
rmrk: item.rmrk || ''
|
||||
admdstNm: item.admdstNm ?? "",
|
||||
pumpstNm: item.fcltNm ?? "",
|
||||
impdFcltPstnCn: item.fcltPstnCn ?? "",
|
||||
pumpSeNm: item.pumpSeNm ?? "",
|
||||
swtrmtFcltNm: item.swtrmtFcltNm ?? "",
|
||||
oprtngStrtYmd: item.oprtngStrtYmd ?? "",
|
||||
exlnPumpN4RsrvCnt: item.exlnPumpN1RsrvCnt ?? 0,
|
||||
rmrk: item.rmrk ?? "",
|
||||
}));
|
||||
|
||||
setStatsStatusInfo(statsInfo);
|
||||
setReviewOpinionContext({
|
||||
statsYr: values.year ?? "",
|
||||
sewerBzmnCd: values.bsnmCd ?? "",
|
||||
statsCd: statsInfo.statsCd ?? "",
|
||||
});
|
||||
setLatestReviewOpinion(statsInfo.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(statsInfo.wrterOpnnCn ?? "");
|
||||
setRowData(toGridRows(convertedData));
|
||||
} else {
|
||||
setRowData([]);
|
||||
setStatsStatusInfo({});
|
||||
setReviewOpinionContext(null);
|
||||
setLatestReviewOpinion("");
|
||||
setLatestProcessHistory("");
|
||||
alert(response.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("중계펌프장 조회 중 에러 발생:", error);
|
||||
alert('데이터 조회 중 오류가 발생했습니다.');
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const approveFunc = () => console.log('approveFunc');
|
||||
const saveFunc = () => console.log('submit');
|
||||
const reviewOpinionFunc = () => console.log('reviewOpinionFunc');
|
||||
const returnFunc = () => console.log('returnFunc');
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.wrterOpnnCn ?? "");
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setStatsStatusInfo((prev) => ({
|
||||
...prev,
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
}));
|
||||
setLatestReviewOpinion(param.splmntOpnnCn);
|
||||
setLatestProcessHistory(param.wrterOpnnCn);
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm fctName={true} 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} />
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && <SubmitButton submitFunc={submitFunc} />}
|
||||
</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">
|
||||
@@ -104,16 +258,48 @@ const PumpingStationRvw = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
excelExportFileSuffix="중계펌프장"
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PumpingStationRvw;
|
||||
export default PumpingStationRvw;
|
||||
|
||||
@@ -65,7 +65,6 @@ export function ReviewSubmitDialog({
|
||||
if (cancelled) return;
|
||||
|
||||
if (response.status !== "success" || !response.data) {
|
||||
alert("제출 정보를 불러오지 못했습니다.");
|
||||
setReviewerInfo(null);
|
||||
return;
|
||||
}
|
||||
@@ -73,7 +72,6 @@ export function ReviewSubmitDialog({
|
||||
setReviewerInfo(response.data);
|
||||
} catch {
|
||||
if (!cancelled) {
|
||||
alert("제출 정보를 불러오지 못했습니다.");
|
||||
setReviewerInfo(null);
|
||||
}
|
||||
} finally {
|
||||
@@ -91,7 +89,7 @@ export function ReviewSubmitDialog({
|
||||
}, [open, context, alert]);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (context == null || reviewerInfo == null) {
|
||||
if (context == null) {
|
||||
alert("제출 정보를 확인한 뒤 다시 시도해주세요.");
|
||||
return;
|
||||
}
|
||||
@@ -105,8 +103,8 @@ export function ReviewSubmitDialog({
|
||||
sewerBzmnCd: context.sewerBzmnCd,
|
||||
statsCd: context.statsCd,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
splmntOpnnCn: reviewerInfo.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: reviewerInfo.wrterOpnnCn ?? "",
|
||||
splmntOpnnCn: reviewerInfo?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: reviewerInfo?.wrterOpnnCn ?? "",
|
||||
};
|
||||
|
||||
const response = await statisticsApi.submitReview(submitParam);
|
||||
@@ -177,7 +175,7 @@ export function ReviewSubmitDialog({
|
||||
type="button"
|
||||
color="primary"
|
||||
onClick={() => void handleConfirm()}
|
||||
disabled={isLoading || isSubmitting || reviewerInfo == null}
|
||||
disabled={isLoading || isSubmitting}
|
||||
>
|
||||
제출
|
||||
</Button>
|
||||
|
||||
@@ -1,53 +1,434 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { TabDataGrid } from "@rsone/components";
|
||||
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 { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
extractSewageExcretaRelatedReviewList,
|
||||
extractSewageExcretaRelatedReviewStatsStatusInfo,
|
||||
mapSewageExcretaRelatedReviewRows,
|
||||
type SewageExcretaRelatedReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-sewage-excreta-related-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewageExcretaRelatedInfoListRvw } from "@/components/util/column/review/SewageExcretaRelatedRvwColumn";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 하수도 통계 관리 12. 하수 및 분뇨 관련 산업
|
||||
const firstTabKey = SewageExcretaRelatedInfoListRvw[0]?.tabKey ?? "ST00120001";
|
||||
|
||||
async function fetchExcretaRelatedReviewTab(
|
||||
tabKey: string,
|
||||
searchParam: SewageExcretaRelatedReviewRequestParams,
|
||||
) {
|
||||
if (tabKey === "ST00120001") {
|
||||
return statisticsManagementApi.getPublicSewageStaffReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00120002") {
|
||||
return statisticsManagementApi.getPublicHumanWasteStaffReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00120003") {
|
||||
return statisticsManagementApi.getHumanWasteCollectionReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00120004") {
|
||||
return statisticsManagementApi.getIndvSwtrmtRelBsnsReviewList(searchParam);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 하수도 통계 검토 12. 하수 및 분뇨 관련 산업
|
||||
const SewageExcretaRelatedRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<string>(firstTabKey);
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
|
||||
Map<string, StatsStatusInfo>
|
||||
>(new Map());
|
||||
|
||||
const approveFunc = () => {
|
||||
console.log('approveFunc');
|
||||
// 승인 로직
|
||||
const searchParamRef = useRef<SewageExcretaRelatedReviewRequestParams | null>(null);
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoadedRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const activeStatsStatusInfo = useMemo(
|
||||
() => tabStatsStatusInfoMap.get(activeTab) ?? {},
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
return SewageExcretaRelatedInfoListRvw.map((tab: ColumnInfoList) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col: ColumnConfig) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||
})),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
return {
|
||||
statsYr: lastSearchValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchValues.bsnmCd ?? "",
|
||||
statsCd: activeStatsStatusInfo.statsCd ?? "",
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchValues.bsnmCd ?? "";
|
||||
const statsCd = activeStatsStatusInfo.statsCd ?? "";
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !statsCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues, userInfo?.instSeCd]);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [activeStatsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
setLastSearchValues(null);
|
||||
searchParamRef.current = null;
|
||||
searchDoneRef.current = false;
|
||||
lazyTabLoadedRef.current.clear();
|
||||
};
|
||||
|
||||
const saveFunc = () => {
|
||||
console.log('submit');
|
||||
// 저장 로직
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLastSearchValues(values);
|
||||
|
||||
const searchParam: SewageExcretaRelatedReviewRequestParams = {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
};
|
||||
|
||||
searchParamRef.current = searchParam;
|
||||
searchDoneRef.current = true;
|
||||
lazyTabLoadedRef.current.clear();
|
||||
|
||||
const response = await fetchExcretaRelatedReviewTab(activeTab, searchParam);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractSewageExcretaRelatedReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, ssi);
|
||||
return next;
|
||||
});
|
||||
const listData = extractSewageExcretaRelatedReviewList(activeTab, response.data);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, toGridRows(mapSewageExcretaRelatedReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setRowDataMap(new Map());
|
||||
alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setRowDataMap(new Map());
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = () => {
|
||||
console.log('reviewOpinionFunc');
|
||||
// 검토의견 작성
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
setActiveTab(tabKey);
|
||||
|
||||
const activeTabData = rowDataMap.get(tabKey);
|
||||
const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0;
|
||||
|
||||
const canLazyLoad =
|
||||
searchParamRef.current &&
|
||||
!hasActiveTabData &&
|
||||
!lazyTabLoadedRef.current.has(tabKey) &&
|
||||
searchDoneRef.current;
|
||||
|
||||
if (!canLazyLoad) return;
|
||||
|
||||
const searchParam = searchParamRef.current;
|
||||
if (!searchParam) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await fetchExcretaRelatedReviewTab(tabKey, searchParam);
|
||||
lazyTabLoadedRef.current.add(tabKey);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractSewageExcretaRelatedReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, ssi);
|
||||
return next;
|
||||
});
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const listData = extractSewageExcretaRelatedReviewList(tabKey, response.data);
|
||||
next.set(tabKey, toGridRows(mapSewageExcretaRelatedReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => {
|
||||
console.log('returnFunc');
|
||||
// 반려
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm fctName={true} />
|
||||
<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} />
|
||||
<div className="relative">
|
||||
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
||||
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={() => submitFunc()} />
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
<TabDataGrid columnInfo={SewageExcretaRelatedInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SewageExcretaRelatedRvw;
|
||||
|
||||
@@ -1,52 +1,435 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { TabDataGrid } from "@rsone/components";
|
||||
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 { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
extractSewageExcretaSlurgeReviewList,
|
||||
extractSewageExcretaSlurgeReviewStatsStatusInfo,
|
||||
mapSewageExcretaSlurgeReviewRows,
|
||||
type SewageExcretaSlurgeReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-sewage-excreta-slurge-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewageExcretaSludgeInfoListRvw } from "@/components/util/column/review/SewageExcretaSlurgeRvwColumn";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 하수도 통계 관리 11. 하수 및 분뇨 찌꺼기(슬러지) 처리
|
||||
const firstTabKey = SewageExcretaSludgeInfoListRvw[0]?.tabKey ?? "ST00110001";
|
||||
|
||||
async function fetchSludgeReviewTab(
|
||||
tabKey: string,
|
||||
searchParam: SewageExcretaSlurgeReviewRequestParams,
|
||||
) {
|
||||
if (tabKey === "ST00110001") {
|
||||
return statisticsManagementApi.getSludgeProcessingReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00110002") {
|
||||
return statisticsManagementApi.getSludgeDigestionTankReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00110003") {
|
||||
return statisticsManagementApi.getSludgeFacilityOperReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00110004") {
|
||||
return statisticsManagementApi.getHumanWasteSludgeReviewList(searchParam);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 하수도 통계 검토 11. 하수 및 분뇨 찌꺼기(슬러지) 처리
|
||||
const SewageExcretaSlurgeRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<string>(firstTabKey);
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
|
||||
Map<string, StatsStatusInfo>
|
||||
>(new Map());
|
||||
|
||||
const approveFunc = () => {
|
||||
console.log('approveFunc');
|
||||
// 승인 로직
|
||||
const searchParamRef = useRef<SewageExcretaSlurgeReviewRequestParams | null>(null);
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoadedRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const activeStatsStatusInfo = useMemo(
|
||||
() => tabStatsStatusInfoMap.get(activeTab) ?? {},
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
return SewageExcretaSludgeInfoListRvw.map((tab: ColumnInfoList) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col: ColumnConfig) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||
})),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
return {
|
||||
statsYr: lastSearchValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchValues.bsnmCd ?? "",
|
||||
statsCd: activeStatsStatusInfo.statsCd ?? "",
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchValues.bsnmCd ?? "";
|
||||
const statsCd = activeStatsStatusInfo.statsCd ?? "";
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !statsCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues, userInfo?.instSeCd]);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [activeStatsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
setLastSearchValues(null);
|
||||
searchParamRef.current = null;
|
||||
searchDoneRef.current = false;
|
||||
lazyTabLoadedRef.current.clear();
|
||||
};
|
||||
|
||||
const saveFunc = () => {
|
||||
console.log('submit');
|
||||
// 저장 로직
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLastSearchValues(values);
|
||||
|
||||
const searchParam: SewageExcretaSlurgeReviewRequestParams = {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
swtrmtFcltNm: values.fctName,
|
||||
};
|
||||
|
||||
searchParamRef.current = searchParam;
|
||||
searchDoneRef.current = true;
|
||||
lazyTabLoadedRef.current.clear();
|
||||
|
||||
const response = await fetchSludgeReviewTab(activeTab, searchParam);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractSewageExcretaSlurgeReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, ssi);
|
||||
return next;
|
||||
});
|
||||
const listData = extractSewageExcretaSlurgeReviewList(activeTab, response.data);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, toGridRows(mapSewageExcretaSlurgeReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setRowDataMap(new Map());
|
||||
alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setRowDataMap(new Map());
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = () => {
|
||||
console.log('reviewOpinionFunc');
|
||||
// 검토의견 작성
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
setActiveTab(tabKey);
|
||||
|
||||
const activeTabData = rowDataMap.get(tabKey);
|
||||
const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0;
|
||||
|
||||
const canLazyLoad =
|
||||
searchParamRef.current &&
|
||||
!hasActiveTabData &&
|
||||
!lazyTabLoadedRef.current.has(tabKey) &&
|
||||
searchDoneRef.current;
|
||||
|
||||
if (!canLazyLoad) return;
|
||||
|
||||
const searchParam = searchParamRef.current;
|
||||
if (!searchParam) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await fetchSludgeReviewTab(tabKey, searchParam);
|
||||
lazyTabLoadedRef.current.add(tabKey);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractSewageExcretaSlurgeReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, ssi);
|
||||
return next;
|
||||
});
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const listData = extractSewageExcretaSlurgeReviewList(tabKey, response.data);
|
||||
next.set(tabKey, toGridRows(mapSewageExcretaSlurgeReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => {
|
||||
console.log('returnFunc');
|
||||
// 반려
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm fctName={true} />
|
||||
<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} />
|
||||
<div className="relative">
|
||||
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
||||
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={() => submitFunc()} />
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
<TabDataGrid columnInfo={SewageExcretaSludgeInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SewageExcretaSlurgeRvw;
|
||||
export default SewageExcretaSlurgeRvw;
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type { SewerPipeMtcRequestParams } from "@/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
SewerPipeMtcRequestParams,
|
||||
StatsStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const firstTabKey = SewagePipeMaintainColumnInfoList[0]?.tabKey ?? "ST00030001";
|
||||
|
||||
@@ -50,17 +62,36 @@ function getListDataByTab(tabKey: string, responseData: unknown): object[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
function extractStatsStatusInfo(data: unknown): StatsStatusInfo | null {
|
||||
if (!data || typeof data !== "object") return null;
|
||||
const ssi = (data as { statsStatusInfo?: StatsStatusInfo }).statsStatusInfo;
|
||||
return ssi && typeof ssi === "object" ? ssi : null;
|
||||
}
|
||||
|
||||
// 하수도 통계 검토 03. 하수관로 유지 관리
|
||||
const SewagePipeMaintainRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [activeTab, setActiveTab] = useState<string>(firstTabKey);
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
|
||||
Map<string, StatsStatusInfo>
|
||||
>(new Map());
|
||||
|
||||
const searchParamRef = useRef<SewerPipeMtcRequestParams | null>(null);
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoggedRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const activeStatsStatusInfo = useMemo(
|
||||
() => tabStatsStatusInfoMap.get(activeTab) ?? {},
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
return SewagePipeMaintainColumnInfoList.map((tab: ColumnInfoList) => ({
|
||||
...tab,
|
||||
@@ -72,9 +103,66 @@ const SewagePipeMaintainRvw = () => {
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
return {
|
||||
statsYr: lastSearchValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchValues.bsnmCd ?? "",
|
||||
statsCd: activeStatsStatusInfo.statsCd ?? "",
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchValues.bsnmCd ?? "";
|
||||
const statsCd = activeStatsStatusInfo.statsCd ?? "";
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !statsCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues, userInfo?.instSeCd]);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [activeStatsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
setLastSearchValues(null);
|
||||
searchParamRef.current = null;
|
||||
searchDoneRef.current = false;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
@@ -88,6 +176,7 @@ const SewagePipeMaintainRvw = () => {
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLastSearchValues(values);
|
||||
|
||||
const searchParam: SewerPipeMtcRequestParams = {
|
||||
statsYr: values.year,
|
||||
@@ -103,6 +192,12 @@ const SewagePipeMaintainRvw = () => {
|
||||
const response = await fetchSewagePipeMaintainReviewTab(activeTab, searchParam);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, ssi ?? {});
|
||||
return next;
|
||||
});
|
||||
const listData = getListDataByTab(activeTab, response.data);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
@@ -110,9 +205,11 @@ const SewagePipeMaintainRvw = () => {
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -143,6 +240,12 @@ const SewagePipeMaintainRvw = () => {
|
||||
lazyTabLoggedRef.current.add(tabKey);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, ssi ?? {});
|
||||
return next;
|
||||
});
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const listData = getListDataByTab(tabKey, response.data);
|
||||
@@ -159,18 +262,96 @@ const SewagePipeMaintainRvw = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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={() => { }} />
|
||||
<ApproveButton approveFunc={() => { }} />
|
||||
<ReturnButton returnFunc={() => { }} />
|
||||
<SaveButton saveFunc={() => { }} />
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={() => submitFunc()} />
|
||||
)}
|
||||
</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">
|
||||
@@ -228,14 +409,39 @@ const SewagePipeMaintainRvw = () => {
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SewagePipeMaintainRvw;
|
||||
export default SewagePipeMaintainRvw;
|
||||
|
||||
@@ -32,7 +32,7 @@ import type {
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const firstTabKey = SewagePipeColumnInfoListRvw[0]?.tabKey ?? "ST00020001";
|
||||
|
||||
@@ -459,6 +459,7 @@ const SewagePipeRvw = () => {
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
|
||||
@@ -1,19 +1,72 @@
|
||||
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 { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useAlertStore, useUserStore } 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";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type { SewerReservoirRequestParams } from "@/entities/statistics/model/sewage-reservoir-pump-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewageReservoirPumpInfoListRvw } from "@/components/util/column/review/SewageReservoirPumpRvwColumn";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const firstTabKey = SewageReservoirPumpInfoListRvw[0]?.tabKey ?? "ST00040001";
|
||||
|
||||
async function fetchSewageReservoirPumpReviewTab(
|
||||
tabKey: string,
|
||||
searchParam: SewerReservoirRequestParams,
|
||||
) {
|
||||
if (tabKey === "ST00040001") {
|
||||
return statisticsManagementApi.getSewerReservoirList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00040002") {
|
||||
return statisticsManagementApi.getPumpStationList(searchParam);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getListDataByTab(tabKey: string, responseData: unknown): object[] {
|
||||
if (Array.isArray(responseData)) {
|
||||
return responseData as object[];
|
||||
}
|
||||
if (!responseData || typeof responseData !== "object") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = responseData as Record<string, unknown>;
|
||||
if (tabKey === "ST00040001") {
|
||||
const list = data.sewerReservoirList ?? data.facilitiesList;
|
||||
return Array.isArray(list) ? (list as object[]) : [];
|
||||
}
|
||||
if (tabKey === "ST00040002") {
|
||||
const list = data.pumpStationList ?? data.pumpingStationList;
|
||||
return Array.isArray(list) ? (list as object[]) : [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function extractStatsStatusInfo(data: unknown): StatsStatusInfo | null {
|
||||
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
||||
const ssi = (data as { statsStatusInfo?: StatsStatusInfo }).statsStatusInfo;
|
||||
return ssi && typeof ssi === "object" ? ssi : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 하수도 통계 검토 04. 하수저류 시설 및 빗물 펌프장
|
||||
@@ -22,11 +75,26 @@ import type { SewerReservoirRequestParams } from "@/entities/statistics/model/se
|
||||
*/
|
||||
const SewageReservoirPumpRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
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 [activeTab, setActiveTab] = useState<string>(firstTabKey);
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
|
||||
Map<string, StatsStatusInfo>
|
||||
>(new Map());
|
||||
|
||||
const searchParamRef = useRef<SewerReservoirRequestParams | null>(null);
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoggedRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const activeStatsStatusInfo = useMemo(
|
||||
() => tabStatsStatusInfoMap.get(activeTab) ?? {},
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
return SewageReservoirPumpInfoListRvw.map((tab: ColumnInfoList) => ({
|
||||
@@ -39,44 +107,116 @@ const SewageReservoirPumpRvw = () => {
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const activeStatsCd = activeStatsStatusInfo.statsCd ?? activeTab;
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
return {
|
||||
statsYr: lastSearchValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchValues.bsnmCd ?? "",
|
||||
statsCd: activeStatsCd,
|
||||
};
|
||||
}, [activeStatsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchValues.bsnmCd ?? "";
|
||||
const statsCd = activeStatsCd;
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !statsCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [activeStatsCd, lastSearchValues, userInfo?.instSeCd]);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [activeStatsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab('ST00040001');
|
||||
setActiveTab(firstTabKey);
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
setLastSearchValues(null);
|
||||
searchParamRef.current = null;
|
||||
searchDoneRef.current = false;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === 'ALL') {
|
||||
alert('시도를 선택해주세요.');
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTab('ST00040001');
|
||||
setRowDataMap(new Map());
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLastSearchValues(values);
|
||||
|
||||
const searchParam: SewerReservoirRequestParams = {
|
||||
const searchParam: SewerReservoirRequestParams = {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
};
|
||||
|
||||
searchParamRef.current = searchParam;
|
||||
searchDoneRef.current = true;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
|
||||
const response = await statisticsManagementApi.getSewerReservoirList(searchParam);
|
||||
const response = await fetchSewageReservoirPumpReviewTab(activeTab, 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);
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, ssi ?? {});
|
||||
return next;
|
||||
});
|
||||
const listData = getListDataByTab(activeTab, response.data);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, toGridRows(listData));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert('데이터 조회 중 오류가 발생했습니다.');
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -85,38 +225,119 @@ const SewageReservoirPumpRvw = () => {
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
setActiveTab(tabKey);
|
||||
|
||||
const existingAData = rowDataMap.get('ST00040001');
|
||||
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);
|
||||
const canLazyLoad =
|
||||
searchParamRef.current &&
|
||||
!hasActiveTabData &&
|
||||
!lazyTabLoggedRef.current.has(tabKey) &&
|
||||
searchDoneRef.current;
|
||||
|
||||
let response;
|
||||
if (!canLazyLoad) return;
|
||||
|
||||
if (tabKey === 'ST00040001') {
|
||||
response = await statisticsManagementApi.getSewerReservoirList(searchParamRef.current);
|
||||
} else if (tabKey === 'ST00040002') {
|
||||
response = await statisticsManagementApi.getPumpStationList(searchParamRef.current);
|
||||
}
|
||||
const searchParam = searchParamRef.current;
|
||||
if (!searchParam) return;
|
||||
|
||||
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);
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await fetchSewageReservoirPumpReviewTab(tabKey, searchParam);
|
||||
lazyTabLoggedRef.current.add(tabKey);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, ssi ?? {});
|
||||
return next;
|
||||
});
|
||||
const listData = getListDataByTab(tabKey, response.data);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, toGridRows(listData));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -124,14 +345,19 @@ const SewageReservoirPumpRvw = () => {
|
||||
<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={() => { }} />
|
||||
<ApproveButton approveFunc={() => { }} />
|
||||
<ReturnButton returnFunc={() => { }} />
|
||||
<SaveButton saveFunc={() => { }} />
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={() => submitFunc()} />
|
||||
)}
|
||||
</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">
|
||||
@@ -141,16 +367,87 @@ const SewageReservoirPumpRvw = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TabDataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
rowDataMap={rowDataMap}
|
||||
setRowDataMap={setRowDataMap}
|
||||
activeTab={activeTab}
|
||||
onActiveTabChange={handleActiveTabChange}
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SewageReservoirPumpRvw;
|
||||
export default SewageReservoirPumpRvw;
|
||||
|
||||
@@ -1,54 +1,432 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { TabDataGrid } from "@rsone/components";
|
||||
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 { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
extractSewerageFinanceReviewList,
|
||||
extractSewerageFinanceReviewStatsStatusInfo,
|
||||
mapSewerageFinanceReviewRows,
|
||||
type SewerageFinanceReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-sewerage-finance-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewerageFinanceInfoListRvw } from "@/components/util/column/review/SewerageFinanceRvwColumn";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const firstTabKey = SewerageFinanceInfoListRvw[0]?.tabKey ?? "ST00090001";
|
||||
|
||||
// 하수도 통계 관리 09. 하수도 재정
|
||||
async function fetchSewerageFinanceReviewTab(
|
||||
tabKey: string,
|
||||
searchParam: SewerageFinanceReviewRequestParams,
|
||||
) {
|
||||
if (tabKey === "ST00090001") {
|
||||
return statisticsManagementApi.getSewerageFinanceSettleReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00090002") {
|
||||
return statisticsManagementApi.getSewerageChargeReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00090003") {
|
||||
return statisticsManagementApi.getSewerageUsageFeeReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00090004") {
|
||||
return statisticsManagementApi.getCauserBurdenReviewList(searchParam);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 하수도 통계 검토 09. 하수도 재정
|
||||
const SewerageFinanceRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [activeTab, setActiveTab] = useState<string>(firstTabKey);
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
|
||||
Map<string, StatsStatusInfo>
|
||||
>(new Map());
|
||||
|
||||
const approveFunc = () => {
|
||||
console.log('approveFunc');
|
||||
// 승인 로직
|
||||
const searchParamRef = useRef<SewerageFinanceReviewRequestParams | null>(null);
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoggedRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const activeStatsStatusInfo = useMemo(
|
||||
() => tabStatsStatusInfoMap.get(activeTab) ?? {},
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
return SewerageFinanceInfoListRvw.map((tab: ColumnInfoList) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col: ColumnConfig) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||
})),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
return {
|
||||
statsYr: lastSearchValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchValues.bsnmCd ?? "",
|
||||
statsCd: activeStatsStatusInfo.statsCd ?? "",
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchValues.bsnmCd ?? "";
|
||||
const statsCd = activeStatsStatusInfo.statsCd ?? "";
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !statsCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues, userInfo?.instSeCd]);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [activeStatsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
setLastSearchValues(null);
|
||||
searchParamRef.current = null;
|
||||
searchDoneRef.current = false;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
};
|
||||
|
||||
const saveFunc = () => {
|
||||
console.log('submit');
|
||||
// 저장 로직
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLastSearchValues(values);
|
||||
|
||||
const searchParam: SewerageFinanceReviewRequestParams = {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
};
|
||||
|
||||
searchParamRef.current = searchParam;
|
||||
searchDoneRef.current = true;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
|
||||
const response = await fetchSewerageFinanceReviewTab(activeTab, searchParam);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractSewerageFinanceReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, ssi);
|
||||
return next;
|
||||
});
|
||||
const listData = extractSewerageFinanceReviewList(activeTab, response.data);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, toGridRows(mapSewerageFinanceReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = () => {
|
||||
console.log('reviewOpinionFunc');
|
||||
// 검토의견 작성
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
setActiveTab(tabKey);
|
||||
|
||||
const activeTabData = rowDataMap.get(tabKey);
|
||||
const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0;
|
||||
|
||||
const canLazyLoad =
|
||||
searchParamRef.current &&
|
||||
!hasActiveTabData &&
|
||||
!lazyTabLoggedRef.current.has(tabKey) &&
|
||||
searchDoneRef.current;
|
||||
|
||||
if (!canLazyLoad) return;
|
||||
|
||||
const searchParam = searchParamRef.current;
|
||||
if (!searchParam) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await fetchSewerageFinanceReviewTab(tabKey, searchParam);
|
||||
lazyTabLoggedRef.current.add(tabKey);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractSewerageFinanceReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, ssi);
|
||||
return next;
|
||||
});
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const listData = extractSewerageFinanceReviewList(tabKey, response.data);
|
||||
next.set(tabKey, toGridRows(mapSewerageFinanceReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => {
|
||||
console.log('returnFunc');
|
||||
// 반려
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm fctName={true} />
|
||||
<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} />
|
||||
<div className="relative">
|
||||
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
||||
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={() => submitFunc()} />
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
<TabDataGrid columnInfo={SewerageFinanceInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SewerageFinanceRvw;
|
||||
export default SewerageFinanceRvw;
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
@@ -245,6 +245,7 @@ const SeweragePenetrationRateRvw = () => {
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
|
||||
<ReviewOpinionDialog
|
||||
|
||||
@@ -1,53 +1,433 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { TabDataGrid } from "@rsone/components";
|
||||
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 { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
extractSewerageServiceLevelReviewList,
|
||||
extractSewerageServiceLevelReviewStatsStatusInfo,
|
||||
mapSewerageServiceLevelReviewRows,
|
||||
type SewerageServiceLevelReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-sewerage-service-level-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type {
|
||||
ColumnConfig,
|
||||
ColumnInfo,
|
||||
ColumnInfoList,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewerageServiceLevelInfoListRvw } from "@/components/util/column/review/SewerageServiceLevelRvwColumn";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 하수도 통계 관리 13. 하수도 서비스 수준
|
||||
const firstTabKey = SewerageServiceLevelInfoListRvw[0]?.tabKey ?? "ST00130001";
|
||||
|
||||
async function fetchServiceLevelReviewTab(
|
||||
tabKey: string,
|
||||
searchParam: SewerageServiceLevelReviewRequestParams,
|
||||
) {
|
||||
if (tabKey === "ST00130001") {
|
||||
return statisticsManagementApi.getSewageComplaintReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00130002") {
|
||||
return statisticsManagementApi.getResidentFriendlyFacilityReviewList(searchParam);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 하수도 통계 검토 13. 하수도 서비스 수준
|
||||
const SewerageServiceLevelRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<string>(firstTabKey);
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
|
||||
Map<string, StatsStatusInfo>
|
||||
>(new Map());
|
||||
|
||||
const approveFunc = () => {
|
||||
console.log('approveFunc');
|
||||
// 승인 로직
|
||||
const searchParamRef = useRef<SewerageServiceLevelReviewRequestParams | null>(null);
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoadedRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const activeStatsStatusInfo = useMemo(
|
||||
() => tabStatsStatusInfoMap.get(activeTab) ?? {},
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
return SewerageServiceLevelInfoListRvw.map((tab: ColumnInfoList) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col: ColumnConfig) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||
})),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
return {
|
||||
statsYr: lastSearchValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchValues.bsnmCd ?? "",
|
||||
statsCd: activeStatsStatusInfo.statsCd ?? "",
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchValues.bsnmCd ?? "";
|
||||
const statsCd = activeStatsStatusInfo.statsCd ?? "";
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !statsCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues, userInfo?.instSeCd]);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [activeStatsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
setLastSearchValues(null);
|
||||
searchParamRef.current = null;
|
||||
searchDoneRef.current = false;
|
||||
lazyTabLoadedRef.current.clear();
|
||||
};
|
||||
|
||||
const saveFunc = () => {
|
||||
console.log('submit');
|
||||
// 저장 로직
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLastSearchValues(values);
|
||||
|
||||
const searchParam: SewerageServiceLevelReviewRequestParams = {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
swtrmtFcltNm: values.fctName,
|
||||
};
|
||||
|
||||
searchParamRef.current = searchParam;
|
||||
searchDoneRef.current = true;
|
||||
lazyTabLoadedRef.current.clear();
|
||||
|
||||
const response = await fetchServiceLevelReviewTab(activeTab, searchParam);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractSewerageServiceLevelReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, ssi);
|
||||
return next;
|
||||
});
|
||||
const listData = extractSewerageServiceLevelReviewList(activeTab, response.data);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, toGridRows(mapSewerageServiceLevelReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setRowDataMap(new Map());
|
||||
alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setRowDataMap(new Map());
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = () => {
|
||||
console.log('reviewOpinionFunc');
|
||||
// 검토의견 작성
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
setActiveTab(tabKey);
|
||||
|
||||
const activeTabData = rowDataMap.get(tabKey);
|
||||
const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0;
|
||||
|
||||
const canLazyLoad =
|
||||
searchParamRef.current &&
|
||||
!hasActiveTabData &&
|
||||
!lazyTabLoadedRef.current.has(tabKey) &&
|
||||
searchDoneRef.current;
|
||||
|
||||
if (!canLazyLoad) return;
|
||||
|
||||
const searchParam = searchParamRef.current;
|
||||
if (!searchParam) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await fetchServiceLevelReviewTab(tabKey, searchParam);
|
||||
lazyTabLoadedRef.current.add(tabKey);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractSewerageServiceLevelReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, ssi);
|
||||
return next;
|
||||
});
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const listData = extractSewerageServiceLevelReviewList(tabKey, response.data);
|
||||
next.set(tabKey, toGridRows(mapSewerageServiceLevelReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => {
|
||||
console.log('returnFunc');
|
||||
// 반려
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm fctName={true} />
|
||||
<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} />
|
||||
<div className="relative">
|
||||
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
||||
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={() => submitFunc()} />
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
<TabDataGrid columnInfo={SewerageServiceLevelInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SewerageServiceLevelRvw;
|
||||
|
||||
@@ -1,51 +1,436 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { TabDataGrid } from "@rsone/components";
|
||||
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 { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
extractWaterReuseReviewList,
|
||||
extractWaterReuseReviewStatsStatusInfo,
|
||||
mapWaterReuseReviewRows,
|
||||
type WaterReuseReviewRequestParams,
|
||||
} from "@/entities/statistics/model/statistics-water-reuse-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
ReviewOpinionSaveRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { WaterReuseInfoListRvw } from "@/components/util/column/review/WaterReuseRvwColumn";
|
||||
import { reviewRowTypeCurrCellStyleCallback, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
import { WaterReuseInfoList } from "@/components/util/column/review/WaterReuseRvwColumn";
|
||||
const firstTabKey = WaterReuseInfoListRvw[0]?.tabKey ?? "ST00100001";
|
||||
|
||||
async function fetchWaterReuseReviewTab(
|
||||
tabKey: string,
|
||||
searchParam: WaterReuseReviewRequestParams,
|
||||
) {
|
||||
if (tabKey === "ST00100001") {
|
||||
return statisticsManagementApi.getWaterReuseReuseStatusReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00100002") {
|
||||
return statisticsManagementApi.getWaterReuseGrayWaterReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00100003") {
|
||||
return statisticsManagementApi.getWaterReuseRainwaterReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00100004") {
|
||||
return statisticsManagementApi.getWaterReuseRegStatusReviewList(searchParam);
|
||||
}
|
||||
if (tabKey === "ST00100005") {
|
||||
return statisticsManagementApi.getWaterReuseSncnStatusReviewList(searchParam);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 하수도 통계 검토 10. 물 재이용
|
||||
const WaterReuseRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<string>(firstTabKey);
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
|
||||
Map<string, StatsStatusInfo>
|
||||
>(new Map());
|
||||
|
||||
const approveFunc = () => {
|
||||
console.log('approveFunc');
|
||||
// 승인 로직
|
||||
const searchParamRef = useRef<WaterReuseReviewRequestParams | null>(null);
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoadedRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const activeStatsStatusInfo = useMemo(
|
||||
() => tabStatsStatusInfoMap.get(activeTab) ?? {},
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
return WaterReuseInfoListRvw.map((tab: ColumnInfoList) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col: ColumnConfig) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||
})),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
return {
|
||||
statsYr: lastSearchValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchValues.bsnmCd ?? "",
|
||||
statsCd: activeStatsStatusInfo.statsCd ?? "",
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchValues.bsnmCd ?? "";
|
||||
const statsCd = activeStatsStatusInfo.statsCd ?? "";
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !statsCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
statsCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues, userInfo?.instSeCd]);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
: "";
|
||||
const prvn =
|
||||
statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [activeStatsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setSubmitDialogOpen(false);
|
||||
setLastSearchValues(null);
|
||||
searchParamRef.current = null;
|
||||
searchDoneRef.current = false;
|
||||
lazyTabLoadedRef.current.clear();
|
||||
};
|
||||
|
||||
const saveFunc = () => {
|
||||
console.log('submit');
|
||||
// 저장 로직
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLastSearchValues(values);
|
||||
|
||||
const searchParam: WaterReuseReviewRequestParams = {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
swtrmtFcltNm: values.fctName,
|
||||
};
|
||||
|
||||
searchParamRef.current = searchParam;
|
||||
searchDoneRef.current = true;
|
||||
lazyTabLoadedRef.current.clear();
|
||||
|
||||
const response = await fetchWaterReuseReviewTab(activeTab, searchParam);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractWaterReuseReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, ssi);
|
||||
return next;
|
||||
});
|
||||
const listData = extractWaterReuseReviewList(activeTab, response.data);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, toGridRows(mapWaterReuseReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = () => {
|
||||
console.log('reviewOpinionFunc');
|
||||
// 검토의견 작성
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
setActiveTab(tabKey);
|
||||
|
||||
const activeTabData = rowDataMap.get(tabKey);
|
||||
const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0;
|
||||
|
||||
const canLazyLoad =
|
||||
searchParamRef.current &&
|
||||
!hasActiveTabData &&
|
||||
!lazyTabLoadedRef.current.has(tabKey) &&
|
||||
searchDoneRef.current;
|
||||
|
||||
if (!canLazyLoad) return;
|
||||
|
||||
const searchParam = searchParamRef.current;
|
||||
if (!searchParam) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await fetchWaterReuseReviewTab(tabKey, searchParam);
|
||||
lazyTabLoadedRef.current.add(tabKey);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
const ssi = extractWaterReuseReviewStatsStatusInfo(response.data);
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, ssi);
|
||||
return next;
|
||||
});
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const listData = extractWaterReuseReviewList(tabKey, response.data);
|
||||
next.set(tabKey, toGridRows(mapWaterReuseReviewRows(listData)));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => {
|
||||
console.log('returnFunc');
|
||||
// 반려
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm fctName={true} />
|
||||
<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} />
|
||||
<div className="relative">
|
||||
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
||||
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={() => submitFunc()} />
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={reviewRowTypeCurrCellStyleCallback}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
<TabDataGrid columnInfo={WaterReuseInfoList} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
onSubmitted={async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WaterReuseRvw;
|
||||
export default WaterReuseRvw;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
@@ -118,7 +118,7 @@ const loadGridData = async (tabKey: string, values: SearchFormValues): Promise<b
|
||||
} else {
|
||||
lastSearchedValuesRef.current = null;
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
lastSearchedValuesRef.current = null;
|
||||
} finally {
|
||||
@@ -137,7 +137,7 @@ const loadGridData = async (tabKey: string, values: SearchFormValues): Promise<b
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await loadGridData(tabKey, values);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useAlertStore } from "@/shared/lib";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
|
||||
@@ -1,118 +1,298 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// 하수도 통계 관리 07.분뇨처리 시설
|
||||
const rawServiceLevelList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
tabLabel: '민원',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
/* 1. 기본 정보 */
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
import type { GridView } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
footerDecimal1ArrayByDivision,
|
||||
footerDecimal3ArrayByDivision,
|
||||
footerIntArrayByDivision,
|
||||
} from "@/components/util/grid/footerSummary";
|
||||
|
||||
/* 2. 하수처리 인구 */
|
||||
{ name: 'POPLTN_SM', fieldName: '하수처리인구(명)', header: { text: '하수처리인구\n(명)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
const getDivisionFooterLabel = (grid: GridView, divisionIndex: number, withSuffix: boolean) => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const uniqueValues = new Set<string>();
|
||||
|
||||
/* 3. 민원유형 (단위: 건) */
|
||||
{ name: 'CVPL_CM', fieldName: '총계(민원)', header: { text: '총계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_DCWTR_QLTWTR', fieldName: '악취', header: { text: '악취' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_BSML', fieldName: '하수관로', header: { text: '하수관로' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_FIVW', fieldName: '배수설비', header: { text: '배수설비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_POLLUTN_ACDNT', fieldName: '맨홀', header: { text: '맨홀' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_SSOS', fieldName: '정화조', header: { text: '정화조' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_CHRGE', fieldName: '하수도요금', header: { text: '하수도요금' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_ETC', fieldName: '기타(민원)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
|
||||
/* 4. 성과 지표 및 비고 */
|
||||
{ name: 'STDR_POPLTN_PER_CVPL', fieldName: '기준인구당 민원건수(건/10,000명)', header: { text: '기준인구당\n민원건수\n(건/10,000명)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } }
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN',
|
||||
'POPLTN_SM',
|
||||
{
|
||||
name: 'complaintTypeGroup',
|
||||
header: { text: '민원유형(건)' },
|
||||
items: [
|
||||
'CVPL_CM',
|
||||
'CVPL_DCWTR_QLTWTR',
|
||||
'CVPL_BSML',
|
||||
'CVPL_FIVW',
|
||||
'CVPL_POLLUTN_ACDNT',
|
||||
'CVPL_SSOS',
|
||||
'CVPL_CHRGE',
|
||||
'CVPL_ETC'
|
||||
]
|
||||
},
|
||||
'STDR_POPLTN_PER_CVPL',
|
||||
'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'b',
|
||||
tabLabel: '주민친화시설',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 5,
|
||||
columns: [
|
||||
/* 1. 기본 및 시설 정보 */
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'BSC_FCT_NM', fieldName: '처리장명', header: { text: '처리장명' } },
|
||||
{ name: 'FCT_CPC', fieldName: '시설용량(㎥/일)', header: { text: '시설용량\n(㎥/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
|
||||
/* 2. 처리장 전체 면적 */
|
||||
{ name: 'PRCPLT_AR', fieldName: '처리장 면적(㎡)', header: { text: '처리장\n면적(㎡)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 3. 주민친화시설 면적 상세 (단위: ㎡) */
|
||||
{ name: 'TOTAL_AR', fieldName: '총계(시설)', header: { text: '총계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'RESTRM_AR', fieldName: '휴게시설', header: { text: '휴게시설' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CHILDPLAY_AR', fieldName: '어린이놀이공간', header: { text: '어린이놀이공간' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ALSFC_AR', fieldName: '체력단련시설', header: { text: '체력단련시설' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'PLGRND_AR', fieldName: '운동시설', header: { text: '운동시설' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'PRKPLCE_AR', fieldName: '주차장', header: { text: '주차장' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CTLSTT_AR', fieldName: '교육 및 문화시설', header: { text: '교육 및\n문화시설' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'NRPRK_AR', fieldName: '습지조성(생태공원)', header: { text: '습지조성\n(생태공원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ETC_AR', fieldName: '기타(시설)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
|
||||
/* 4. 비고 */
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } }
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'BSC_FCT_NM',
|
||||
'FCT_CPC',
|
||||
'YR_GBN',
|
||||
'PRCPLT_AR',
|
||||
{
|
||||
name: 'friendlyFacilityGroup',
|
||||
header: { text: '주민친화시설 면적(㎡)' },
|
||||
items: [
|
||||
'TOTAL_AR',
|
||||
'RESTRM_AR',
|
||||
'CHILDPLAY_AR',
|
||||
'ALSFC_AR',
|
||||
'PLGRND_AR',
|
||||
'PRKPLCE_AR',
|
||||
'CTLSTT_AR',
|
||||
'NRPRK_AR',
|
||||
'ETC_AR'
|
||||
]
|
||||
},
|
||||
'RMK'
|
||||
]
|
||||
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 withSuffix ? "합계" : "";
|
||||
}
|
||||
|
||||
const label = divisionValues[divisionIndex] ?? "";
|
||||
return withSuffix ? `${label}합계` : label;
|
||||
};
|
||||
|
||||
const divisionFooterCallbacks = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) },
|
||||
];
|
||||
|
||||
const divisionFooterLabelOnly = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) },
|
||||
];
|
||||
|
||||
export const ExcretaTreatmentFacilityInfoList = rawServiceLevelList.map(tab => ({
|
||||
// 하수도 통계 검토 07. 분뇨처리시설
|
||||
const rawExcretaTreatmentFacilityList = [
|
||||
{
|
||||
tabKey: "ST00070001",
|
||||
tabLabel: "분뇨처리시설",
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 4,
|
||||
columns: [
|
||||
{
|
||||
name: "ctpvNm",
|
||||
fieldName: "ctpvNm",
|
||||
header: { text: "시도" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
footer: divisionFooterCallbacks,
|
||||
},
|
||||
{
|
||||
name: "sggNm",
|
||||
fieldName: "sggNm",
|
||||
header: { text: "시군구" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
},
|
||||
{
|
||||
name: "displayYear",
|
||||
fieldName: "displayYear",
|
||||
header: { text: "구분" },
|
||||
sortdisable: true,
|
||||
footer: divisionFooterLabelOnly,
|
||||
},
|
||||
{ name: "fcltNm", fieldName: "fcltNm", header: { text: "시설명" } },
|
||||
{ name: "fcltPstnCn", fieldName: "fcltPstnCn", header: { text: "소재지" } },
|
||||
{ name: "telNo", fieldName: "telNo", header: { text: "전화번호" } },
|
||||
{ name: "cmptnStrtYmd", fieldName: "cmptnStrtYmd", header: { text: "준공일" } },
|
||||
{ name: "oprtngStrtYmd", fieldName: "oprtngStrtYmd", header: { text: "가동개시일" } },
|
||||
{
|
||||
name: "wholPjtcoAmt",
|
||||
fieldName: "wholPjtcoAmt",
|
||||
header: { text: "사업비\n(백만원)" },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
},
|
||||
{
|
||||
name: "prcsFcltCpct",
|
||||
fieldName: "prcsFcltCpct",
|
||||
header: { text: "시설용량\n(㎥/일)" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
},
|
||||
{
|
||||
name: "cpct",
|
||||
fieldName: "cpct",
|
||||
header: { text: "처리량\n(㎥/일)" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
},
|
||||
{
|
||||
name: "oprtngDayCnt",
|
||||
fieldName: "oprtngDayCnt",
|
||||
header: { text: "가동일수\n(일/년)" },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
},
|
||||
{ name: "prcsMthdExpln", fieldName: "prcsMthdExpln", header: { text: "처리공법" } },
|
||||
{ name: "linkTmplntCn", fieldName: "linkTmplntCn", header: { text: "연계처리장명" } },
|
||||
{
|
||||
name: "linkPrcsQnt",
|
||||
fieldName: "linkPrcsQnt",
|
||||
header: { text: "연계처리량\n(㎥/일)" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
},
|
||||
{
|
||||
name: "inflwBodWqVl",
|
||||
fieldName: "inflwBodWqVl",
|
||||
header: { text: "BOD" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
{
|
||||
name: "inflwTocWqVl",
|
||||
fieldName: "inflwTocWqVl",
|
||||
header: { text: "TOC" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
{
|
||||
name: "inflwSsWqVl",
|
||||
fieldName: "inflwSsWqVl",
|
||||
header: { text: "SS" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
{
|
||||
name: "inflwTnWqVl",
|
||||
fieldName: "inflwTnWqVl",
|
||||
header: { text: "T-N" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
{
|
||||
name: "inflwTpWqVl",
|
||||
fieldName: "inflwTpWqVl",
|
||||
header: { text: "T-P" },
|
||||
footer: footerDecimal3ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.000",
|
||||
},
|
||||
{
|
||||
name: "inflwClfgCnt",
|
||||
fieldName: "inflwClfgCnt",
|
||||
header: { text: "대장균\n군수" },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
},
|
||||
{
|
||||
name: "dschgBodWqVl",
|
||||
fieldName: "dschgBodWqVl",
|
||||
header: { text: "BOD" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
{
|
||||
name: "dschgTocWqVl",
|
||||
fieldName: "dschgTocWqVl",
|
||||
header: { text: "TOC" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
{
|
||||
name: "dschgSsWqVl",
|
||||
fieldName: "dschgSsWqVl",
|
||||
header: { text: "SS" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
{
|
||||
name: "dschgTnWqVl",
|
||||
fieldName: "dschgTnWqVl",
|
||||
header: { text: "T-N" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
{
|
||||
name: "dschgTpWqVl",
|
||||
fieldName: "dschgTpWqVl",
|
||||
header: { text: "T-P" },
|
||||
footer: footerDecimal3ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.000",
|
||||
},
|
||||
{
|
||||
name: "dschgClfgCnt",
|
||||
fieldName: "dschgClfgCnt",
|
||||
header: { text: "대장균\n군수" },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
},
|
||||
{ name: "wtsysNm", fieldName: "wtsysNm", header: { text: "수계" } },
|
||||
{ name: "trbtrNm", fieldName: "trbtrNm", header: { text: "지류" } },
|
||||
{ name: "msirbCn", fieldName: "msirbCn", header: { text: "중권역" } },
|
||||
{ name: "ssirbCn", fieldName: "ssirbCn", header: { text: "소권역" } },
|
||||
{ name: "operMthdCdCn", fieldName: "operMthdCdCn", header: { text: "운영주체\n(자체/공기업/\n민간위탁)" } },
|
||||
{
|
||||
name: "wholNope",
|
||||
fieldName: "wholNope",
|
||||
header: { text: "직원총수\n(명)" },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
},
|
||||
{
|
||||
name: "sftyAcdntEvntcsCnt",
|
||||
fieldName: "sftyAcdntEvntcsCnt",
|
||||
header: { text: "안전사고\n건수\n(건/인/년)" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
],
|
||||
columnLayout: [
|
||||
{
|
||||
name: "areaGroup",
|
||||
header: { text: "행정구역" },
|
||||
items: [
|
||||
{ column: "ctpvNm", footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] },
|
||||
"sggNm",
|
||||
],
|
||||
},
|
||||
"displayYear",
|
||||
"fcltNm",
|
||||
"fcltPstnCn",
|
||||
"telNo",
|
||||
"cmptnStrtYmd",
|
||||
"oprtngStrtYmd",
|
||||
"wholPjtcoAmt",
|
||||
"prcsFcltCpct",
|
||||
"cpct",
|
||||
"oprtngDayCnt",
|
||||
"prcsMthdExpln",
|
||||
"linkTmplntCn",
|
||||
"linkPrcsQnt",
|
||||
{
|
||||
name: "inflwWqGroup",
|
||||
header: { text: "유입수질(mg/L, 개/mL)" },
|
||||
items: [
|
||||
"inflwBodWqVl",
|
||||
"inflwTocWqVl",
|
||||
"inflwSsWqVl",
|
||||
"inflwTnWqVl",
|
||||
"inflwTpWqVl",
|
||||
"inflwClfgCnt",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "dschgWqGroup",
|
||||
header: { text: "방류수질(mg/L, 개/mL)" },
|
||||
items: [
|
||||
"dschgBodWqVl",
|
||||
"dschgTocWqVl",
|
||||
"dschgSsWqVl",
|
||||
"dschgTnWqVl",
|
||||
"dschgTpWqVl",
|
||||
"dschgClfgCnt",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "wtrAreaGroup",
|
||||
header: { text: "방류수역" },
|
||||
items: ["wtsysNm", "trbtrNm", "msirbCn", "ssirbCn"],
|
||||
},
|
||||
"operMthdCdCn",
|
||||
"wholNope",
|
||||
"sftyAcdntEvntcsCnt",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const ExcretaTreatmentFacilityInfoList = rawExcretaTreatmentFacilityList.map((tab) => ({
|
||||
...tab,
|
||||
columns: tab.columns.map(col => ({
|
||||
columns: tab.columns.map((col) => ({
|
||||
...col,
|
||||
editable: false
|
||||
}))
|
||||
}));
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -1,139 +1,336 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// 하수도 통계 관리 08.개인하수처리시설
|
||||
import type { GridView } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
footerDecimal1ArrayByDivision,
|
||||
footerDecimal2ArrayByDivision,
|
||||
footerIntArrayByDivision,
|
||||
} from "@/components/util/grid/footerSummary";
|
||||
|
||||
const getDivisionFooterLabel = (grid: GridView, divisionIndex: number, withSuffix: boolean) => {
|
||||
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);
|
||||
if (divisionValues.length <= divisionIndex) {
|
||||
return withSuffix ? "합계" : "";
|
||||
}
|
||||
|
||||
const label = divisionValues[divisionIndex] ?? "";
|
||||
return withSuffix ? `${label}합계` : label;
|
||||
};
|
||||
|
||||
const divisionFooterCallbacks = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) },
|
||||
];
|
||||
|
||||
const divisionFooterLabelOnly = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) },
|
||||
];
|
||||
|
||||
const reviewAddressColumns = [
|
||||
{
|
||||
name: "ctpvNm",
|
||||
fieldName: "ctpvNm",
|
||||
header: { text: "시도" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
footer: divisionFooterCallbacks,
|
||||
},
|
||||
{
|
||||
name: "sggNm",
|
||||
fieldName: "sggNm",
|
||||
header: { text: "시군구" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
},
|
||||
{
|
||||
name: "displayYear",
|
||||
fieldName: "displayYear",
|
||||
header: { text: "구분" },
|
||||
sortdisable: true,
|
||||
footer: divisionFooterLabelOnly,
|
||||
},
|
||||
];
|
||||
|
||||
const reviewAddressLayout = {
|
||||
name: "areaGroup",
|
||||
header: { text: "행정구역" },
|
||||
items: [
|
||||
{ column: "ctpvNm", footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] },
|
||||
"sggNm",
|
||||
],
|
||||
};
|
||||
|
||||
const intCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
});
|
||||
|
||||
// 하수도 통계 검토 08. 개인하수처리시설
|
||||
const rawPrivateSewageList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
tabLabel: '처리용량별 오수처리시설 현황',
|
||||
tabKey: "ST00080001",
|
||||
tabLabel: "처리용량별 오수처리시설",
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'STTY_SM', fieldName: '총계', header: { text: '총계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_INNER_SM', fieldName: '계(처리구역 내)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_INNER_2', fieldName: '2이하(처리구역 내)', header: { text: '2이하' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_INNER_3', fieldName: '3~10(처리구역 내)', header: { text: '3~10' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_INNER_11', fieldName: '11~20(처리구역 내)', header: { text: '11~20' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_INNER_21', fieldName: '21~50(처리구역 내)', header: { text: '21~50' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_INNER_51', fieldName: '51~100(처리구역 내)', header: { text: '51~100' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_INNER_101', fieldName: '101~500(처리구역 내)', header: { text: '101~500' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_INNER_501', fieldName: '501이상(처리구역 내)', header: { text: '501이상' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_OUTER_SM', fieldName: '계(처리구역 외)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_OUTER_2', fieldName: '2이하(처리구역 외)', header: { text: '2이하' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_OUTER_3', fieldName: '3~10(처리구역 외)', header: { text: '3~10' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_OUTER_11', fieldName: '11~20(처리구역 외)', header: { text: '11~20' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_OUTER_21', fieldName: '21~50(처리구역 외)', header: { text: '21~50' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_OUTER_51', fieldName: '51~100(처리구역 외)', header: { text: '51~100' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_OUTER_101', fieldName: '101~500(처리구역 외)', header: { text: '101~500' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'STTY_ZONE_OUTER_501', fieldName: '501이상(처리구역 외)', header: { text: '501이상' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
...reviewAddressColumns,
|
||||
intCol("wholDrtwPrcsFcltCnt", "총계"),
|
||||
intCol("wholStaaCnt", "계"),
|
||||
intCol("step1StaaPrcsQnt", "2이하"),
|
||||
intCol("step2StaaPrcsQnt", "3~10"),
|
||||
intCol("step3StaaPrcsQnt", "11~20"),
|
||||
intCol("step4StaaPrcsQnt", "21~50"),
|
||||
intCol("step5StaaPrcsQnt", "51~100"),
|
||||
intCol("step6StaaPrcsQnt", "101~500"),
|
||||
intCol("step7StaaPrcsQnt", "501이상"),
|
||||
intCol("staaExcpWhol", "계"),
|
||||
intCol("step1StaaExcpPrcsQnt", "2이하"),
|
||||
intCol("step2StaaExcpPrcsQnt", "3~10"),
|
||||
intCol("step3StaaExcpPrcsQnt", "11~20"),
|
||||
intCol("step4StaaExcpPrcsQnt", "21~50"),
|
||||
intCol("step5StaaExcpPrcsQnt", "51~100"),
|
||||
intCol("step6StaaExcpPrcsQnt", "101~500"),
|
||||
intCol("step7StaaExcpPrcsQnt", "501이상"),
|
||||
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN', 'STTY_SM',
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
"wholDrtwPrcsFcltCnt",
|
||||
{
|
||||
name: 'individualGroup', header: { text: '오수처리시설' },
|
||||
name: "privateSewageFacilityGroup",
|
||||
header: { text: "오수처리시설" },
|
||||
items: [
|
||||
{
|
||||
name: 'innerGroup', header: { text: '하수처리구역 내(㎥/일)' },
|
||||
items: ['STTY_ZONE_INNER_SM', 'STTY_ZONE_INNER_2', 'STTY_ZONE_INNER_3', 'STTY_ZONE_INNER_11', 'STTY_ZONE_INNER_21', 'STTY_ZONE_INNER_51', 'STTY_ZONE_INNER_101', 'STTY_ZONE_INNER_501']
|
||||
name: "insideGroup",
|
||||
header: { text: "하수처리구역 내(㎥/일)" },
|
||||
items: [
|
||||
"wholStaaCnt",
|
||||
"step1StaaPrcsQnt",
|
||||
"step2StaaPrcsQnt",
|
||||
"step3StaaPrcsQnt",
|
||||
"step4StaaPrcsQnt",
|
||||
"step5StaaPrcsQnt",
|
||||
"step6StaaPrcsQnt",
|
||||
"step7StaaPrcsQnt",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'outerGroup', header: { text: '하수처리구역 외(㎥/일)' },
|
||||
items: ['STTY_ZONE_OUTER_SM', 'STTY_ZONE_OUTER_2', 'STTY_ZONE_OUTER_3', 'STTY_ZONE_OUTER_11', 'STTY_ZONE_OUTER_21', 'STTY_ZONE_OUTER_51', 'STTY_ZONE_OUTER_101', 'STTY_ZONE_OUTER_501']
|
||||
}
|
||||
]
|
||||
name: "outsideGroup",
|
||||
header: { text: "하수처리구역 외(㎥/일)" },
|
||||
items: [
|
||||
"staaExcpWhol",
|
||||
"step1StaaExcpPrcsQnt",
|
||||
"step2StaaExcpPrcsQnt",
|
||||
"step3StaaExcpPrcsQnt",
|
||||
"step4StaaExcpPrcsQnt",
|
||||
"step5StaaExcpPrcsQnt",
|
||||
"step6StaaExcpPrcsQnt",
|
||||
"step7StaaExcpPrcsQnt",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
'RMK'
|
||||
]
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: 'b',
|
||||
tabLabel: '정화조현황',
|
||||
tabKey: "ST00080002",
|
||||
tabLabel: "정화조현황",
|
||||
rowStartNum: 4,
|
||||
fixedColumns: 1,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'TOT_FCLTY_CNT', fieldName: '총 시설 수(A+D)', header: { text: '총 시설 수\n(A+D)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_SM', fieldName: '총계 A=(B+C)/2', header: { text: '총계\nA=(B+C)/2' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_NPFL', fieldName: '자연유하', header: { text: '자연유하' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_ENFCT_PUMP', fieldName: '강제펌핑식', header: { text: '강제펌핑식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_TFM', fieldName: '살수여상', header: { text: '살수여상' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_ARTM', fieldName: '폭기방법', header: { text: '폭기방법' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_CTARM', fieldName: '접촉폭기', header: { text: '접촉폭기' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_ETC', fieldName: '기타(방식)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_PROC_CPC_SM', fieldName: '계(C)', header: { text: '계(C)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_PROC_CPC_1', fieldName: '10인조 이하', header: { text: '10인조 이하' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_PROC_CPC_2', fieldName: '11~20', header: { text: '11~20' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_PROC_CPC_3', fieldName: '21~30', header: { text: '21~30' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_PROC_CPC_4', fieldName: '31~50', header: { text: '31~50' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_PROC_CPC_5', fieldName: '51~199', header: { text: '51~199' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_PROC_CPC_6', fieldName: '200~499', header: { text: '200~499' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_PROC_CPC_7', fieldName: '500~999', header: { text: '500~999' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'SPZI_PROC_CPC_8', fieldName: '1,000이상', header: { text: '1,000이상' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } }
|
||||
...reviewAddressColumns,
|
||||
intCol("grssFcltCnt", "총 시설 수\n(A+D)"),
|
||||
intCol("staaInsdWhol", "총계\nA=(B+C)/2"),
|
||||
intCol("staaInsdPrcsCpctByWhol", "계(C)"),
|
||||
intCol("staaInsdSttkNprsflCnt", "자연유하"),
|
||||
intCol("step1StaaInsdSttkFrcCnt", "강제펌핑식"),
|
||||
intCol("staaInsdSprktpFltrtbCnt", "살수여상"),
|
||||
intCol("staaInsdAertMthdCnt", "폭기방법"),
|
||||
intCol("staaInsdCntcAertCnt", "접촉폭기"),
|
||||
intCol("staaInsdEtcCnt", "기타"),
|
||||
intCol("step1StaaInsdNope", "10인조\n이하"),
|
||||
intCol("step2StaaInsdNope", "11~20"),
|
||||
intCol("step3StaaInsdNope", "21~30"),
|
||||
intCol("step4StaaInsdNope", "31~50"),
|
||||
intCol("step5StaaInsdNope", "51~199"),
|
||||
intCol("step6StaaInsdNope", "200~499"),
|
||||
intCol("step7StaaInsdNope", "500~999"),
|
||||
intCol("step8StaaInsdNope", "1,000이상"),
|
||||
intCol("staaExcpWhol", "총계\nD=(E+F)/2"),
|
||||
intCol("staaExcpPrcsCpctByWhol", "계(F)"),
|
||||
intCol("staaExcpSttkNprsflCnt", "자연유하"),
|
||||
intCol("step1StaaExcpSttkFrcCnt", "강제펌핑식"),
|
||||
intCol("staaExcpSprktpFltrtbCnt", "살수여상"),
|
||||
intCol("staaExcpAertMthdCnt", "폭기방법"),
|
||||
intCol("staaExcpCntcAertCnt", "접촉폭기"),
|
||||
intCol("staaExcpEtcCnt", "기타"),
|
||||
intCol("step1StaaExcpNope", "10인조\n이하"),
|
||||
intCol("step2StaaExcpNope", "11~20"),
|
||||
intCol("step3StaaExcpNope", "21~30"),
|
||||
intCol("step4StaaExcpNope", "31~50"),
|
||||
intCol("step5StaaExcpNope", "51~199"),
|
||||
intCol("step6StaaExcpNope", "200~499"),
|
||||
intCol("step7StaaExcpNope", "500~999"),
|
||||
intCol("step8StaaExcpNope", "1,000이상"),
|
||||
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN', 'TOT_FCLTY_CNT',
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
"grssFcltCnt",
|
||||
{
|
||||
name: 'innerMainGroup', header: { text: '하수처리구역 내' },
|
||||
name: "insideSewerGroup",
|
||||
header: { text: "하수처리구역 내" },
|
||||
items: [
|
||||
'SPZI_SM',
|
||||
"staaInsdWhol",
|
||||
{
|
||||
name: 'innerMethodGroup', header: { text: '처리방식별' },
|
||||
items: [{ name: 'innerTankGroup', header: { text: '부패탱크' }, items: ['SPZI_NPFL', 'SPZI_ENFCT_PUMP'] }, 'SPZI_TFM', 'SPZI_ARTM', 'SPZI_CTARM', 'SPZI_ETC']
|
||||
name: "insdBGroup",
|
||||
header: { text: "계(B)" },
|
||||
items: [
|
||||
"staaInsdSttkNprsflCnt",
|
||||
"step1StaaInsdSttkFrcCnt",
|
||||
"staaInsdSprktpFltrtbCnt",
|
||||
"staaInsdAertMthdCnt",
|
||||
"staaInsdCntcAertCnt",
|
||||
"staaInsdEtcCnt",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'innerCpcGroup', header: { text: '처리용량별' },
|
||||
items: ['SPZI_PROC_CPC_SM', 'SPZI_PROC_CPC_1', 'SPZI_PROC_CPC_2', 'SPZI_PROC_CPC_3', 'SPZI_PROC_CPC_4', 'SPZI_PROC_CPC_5', 'SPZI_PROC_CPC_6', 'SPZI_PROC_CPC_7', 'SPZI_PROC_CPC_8']
|
||||
}
|
||||
]
|
||||
name: "insdCGroup",
|
||||
header: { text: "처리용량별" },
|
||||
items: [
|
||||
"staaInsdPrcsCpctByWhol",
|
||||
"step1StaaInsdNope",
|
||||
"step2StaaInsdNope",
|
||||
"step3StaaInsdNope",
|
||||
"step4StaaInsdNope",
|
||||
"step5StaaInsdNope",
|
||||
"step6StaaInsdNope",
|
||||
"step7StaaInsdNope",
|
||||
"step8StaaInsdNope",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
'RMK'
|
||||
]
|
||||
{
|
||||
name: "outsideSewerGroup",
|
||||
header: { text: "하수처리구역 외" },
|
||||
items: [
|
||||
"staaExcpWhol",
|
||||
{
|
||||
name: "excpEGroup",
|
||||
header: { text: "계(E)" },
|
||||
items: [
|
||||
"staaExcpSttkNprsflCnt",
|
||||
"step1StaaExcpSttkFrcCnt",
|
||||
"staaExcpSprktpFltrtbCnt",
|
||||
"staaExcpAertMthdCnt",
|
||||
"staaExcpCntcAertCnt",
|
||||
"staaExcpEtcCnt",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "excpFGroup",
|
||||
header: { text: "처리용량별" },
|
||||
items: [
|
||||
"staaExcpPrcsCpctByWhol",
|
||||
"step1StaaExcpNope",
|
||||
"step2StaaExcpNope",
|
||||
"step3StaaExcpNope",
|
||||
"step4StaaExcpNope",
|
||||
"step5StaaExcpNope",
|
||||
"step6StaaExcpNope",
|
||||
"step7StaaExcpNope",
|
||||
"step8StaaExcpNope",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: 'c',
|
||||
tabLabel: '개인하수도 관리지역 지정공고 현황',
|
||||
tabKey: "ST00080003",
|
||||
tabLabel: "개인하수도 관리지역 지정공고 현황",
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 1,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'MGMT_AR', fieldName: '공고지역(개소)', header: { text: '공고지역\n(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'APPN_SCOPE', fieldName: '지정범위(㎢)', header: { text: '지정범위\n(㎢)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'WRRTN_CNT', fieldName: '개소수(정화조)', header: { text: '개소수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'WRRTN_CPC', fieldName: '처리용량(인/일)', header: { text: '처리용량\n(인/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'STTY_CNT', fieldName: '개소수(오수시설)', header: { text: '개소수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'STTY_CPC', fieldName: '처리용량(㎥/일)', header: { text: '처리용량\n(㎥/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
...reviewAddressColumns,
|
||||
intCol("indvSwagePbancRgnCnt", "공고지역\n(개소)"),
|
||||
{
|
||||
name: "indvSwageDsgnScpLen",
|
||||
fieldName: "indvSwageDsgnScpLen",
|
||||
header: { text: "지정범위\n(㎢)" },
|
||||
footer: footerDecimal2ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
},
|
||||
intCol("spctnkGrssFcltCnt", "총시설수"),
|
||||
{
|
||||
name: "spctnkQnt",
|
||||
fieldName: "spctnkQnt",
|
||||
header: { text: "처리용량\n(인/일)" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
},
|
||||
intCol("drtwPrcsFcltGrssFcltCnt", "총시설수"),
|
||||
{
|
||||
name: "drtwPrcsFcltQnt",
|
||||
fieldName: "drtwPrcsFcltQnt",
|
||||
header: { text: "처리용량\n(㎥/일)" },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
},
|
||||
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN',
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
{
|
||||
name: 'mgmtMainGroup', header: { text: '개인하수도관리지역공고현황\n(하수도법 제34조의2)' },
|
||||
name: "groupNotice",
|
||||
header: { text: "개인하수도관리지역공고현황 (하수도법제34조의2)" },
|
||||
items: [
|
||||
'MGMT_AR', 'APPN_SCOPE',
|
||||
{ name: 'septictankGroup', header: { text: '정화조' }, items: ['WRRTN_CNT', 'WRRTN_CPC'] },
|
||||
{ name: 'sewageFacGroup', header: { text: '오수시설' }, items: ['STTY_CNT', 'STTY_CPC'] }
|
||||
]
|
||||
"indvSwagePbancRgnCnt",
|
||||
"indvSwageDsgnScpLen",
|
||||
{
|
||||
name: "septicTankGroup",
|
||||
header: { text: "정화조" },
|
||||
items: ["spctnkGrssFcltCnt", "spctnkQnt"],
|
||||
},
|
||||
{
|
||||
name: "sewageTreatmentGroup",
|
||||
header: { text: "오수처리시설" },
|
||||
items: ["drtwPrcsFcltGrssFcltCnt", "drtwPrcsFcltQnt"],
|
||||
},
|
||||
],
|
||||
},
|
||||
'RMK'
|
||||
]
|
||||
}
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const PrivateSewageFacilityInfoListRvw = rawPrivateSewageList.map(tab => ({
|
||||
export const PrivateSewageFacilityInfoListRvw = rawPrivateSewageList.map((tab) => ({
|
||||
...tab,
|
||||
columns: tab.columns.map(col => ({
|
||||
columns: tab.columns.map((col) => ({
|
||||
...col,
|
||||
editable: false
|
||||
}))
|
||||
}));
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -1,135 +1,276 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// 하수도 통계 관리 12. 하수 및 분뇨 관련 산업
|
||||
const rawExcretaRelatedList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
tabLabel: '공공기관 하수분야',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'OCGNZ', fieldName: '직제', header: { text: '직제' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'TOT_WTR_QRM', fieldName: '직원총수', header: { text: '직원총수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ADMINIST_JSSFC', fieldName: '행정직', header: { text: '행정직' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'TCHNLGY_JSSFC_SM', fieldName: '계(기술직)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ENGRK_JSSFC', fieldName: '토목', header: { text: '토목' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'MCHN_JSSFC', fieldName: '기계', header: { text: '기계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ELCTY_JSSFC', fieldName: '전기', header: { text: '전기' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CHMSTRY_JSSFC', fieldName: '화공', header: { text: '화공' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ENVRN_JSSFC', fieldName: '환경', header: { text: '환경' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'TCHNLGY_ETC', fieldName: '기타(기술직)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ETC', fieldName: '기타', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'OCGNZ', 'YR_GBN', 'TOT_WTR_QRM', 'ADMINIST_JSSFC',
|
||||
{
|
||||
name: 'techJobGroup',
|
||||
header: { text: '기술직' },
|
||||
items: ['TCHNLGY_JSSFC_SM', 'ENGRK_JSSFC', 'MCHN_JSSFC', 'ELCTY_JSSFC', 'CHMSTRY_JSSFC', 'ENVRN_JSSFC', 'TCHNLGY_ETC']
|
||||
},
|
||||
'ETC', 'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'b',
|
||||
tabLabel: '공공기관 분뇨분야',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 1,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'OCGNZ', fieldName: '직제', header: { text: '직제' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'TOT_WTR_QRM', fieldName: '직원총수', header: { text: '직원총수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ADMINIST_JSSFC', fieldName: '행정직', header: { text: '행정직' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'TCHNLGY_JSSFC_SM', fieldName: '계(기술직)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ENGRK_JSSFC', fieldName: '토목', header: { text: '토목' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'MCHN_JSSFC', fieldName: '기계', header: { text: '기계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ELCTY_JSSFC', fieldName: '전기', header: { text: '전기' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CHMSTRY_JSSFC', fieldName: '화공', header: { text: '화공' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ENVRN_JSSFC', fieldName: '환경', header: { text: '환경' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'TCHNLGY_ETC', fieldName: '기타(기술직)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ETC', fieldName: '기타', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'OCGNZ', 'YR_GBN', 'TOT_WTR_QRM', 'ADMINIST_JSSFC',
|
||||
{
|
||||
name: 'techJobGroup',
|
||||
header: { text: '기술직' },
|
||||
items: ['TCHNLGY_JSSFC_SM', 'ENGRK_JSSFC', 'MCHN_JSSFC', 'ELCTY_JSSFC', 'CHMSTRY_JSSFC', 'ENVRN_JSSFC', 'TCHNLGY_ETC']
|
||||
},
|
||||
'ETC', 'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'c',
|
||||
tabLabel: '분뇨수집·운반업',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'ECSCS_SM', fieldName: '업체수(개소)', header: { text: '업체수(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ECSCS_BSMN', fieldName: '종사인원(명)', header: { text: '종사인원(명)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'VHCLE_SM', fieldName: '계(차량)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'THREE_BELOW', fieldName: '3톤이하', header: { text: '3톤이하' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'FOUR_BELOW', fieldName: '4.5톤이하', header: { text: '4.5톤이하' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'EIGHT_BELOW', fieldName: '8톤이하', header: { text: '8톤이하' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ETC_VHCLE', fieldName: '기타(차량)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, // name 중복 방지를 위해 ETC_VHCLE로 수정 권장
|
||||
{ name: 'COLCT_TRSP_ACMSLT', fieldName: '수집·운반실적(㎥/년)', header: { text: '수집·운반실적\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN', 'ECSCS_SM', 'ECSCS_BSMN',
|
||||
{
|
||||
name: 'vehicleGroup',
|
||||
header: { text: '시설(차량) 확보현황(대수)' },
|
||||
items: ['VHCLE_SM', 'THREE_BELOW', 'FOUR_BELOW', 'EIGHT_BELOW', 'ETC_VHCLE']
|
||||
},
|
||||
'COLCT_TRSP_ACMSLT', 'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'd',
|
||||
tabLabel: '개인하수처리관련업',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'CNSTRCT_ENTRPS', fieldName: '업소수(시공업)', header: { text: '업소수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CNSTRCT_BSMN', fieldName: '종사인원(명)_시공업', header: { text: '종사인원(명)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'MNFCTUR_ENTRPS', fieldName: '업소수(제조업)', header: { text: '업소수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'MNFCTUR_BSMN', fieldName: '종사인원(명)_제조업', header: { text: '종사인원(명)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'MGMT_ENTRPS', fieldName: '업소수(관리업)', header: { text: '업소수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'MGMT_BSMN', fieldName: '종사인원(명)_관리업', header: { text: '종사인원(명)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } }
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN',
|
||||
{ name: 'constructGroup', header: { text: '개인하수처리시설 설계ㆍ시공업' }, items: ['CNSTRCT_ENTRPS', 'CNSTRCT_BSMN'] },
|
||||
{ name: 'manufactureGroup', header: { text: '개인하수처리시설 제조업' }, items: ['MNFCTUR_ENTRPS', 'MNFCTUR_BSMN'] },
|
||||
{ name: 'managementGroup', header: { text: '개인하수처리시설 관리업' }, items: ['MGMT_ENTRPS', 'MGMT_BSMN'] },
|
||||
'RMK'
|
||||
]
|
||||
}
|
||||
import type { GridView } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
footerDecimal1ArrayByDivision,
|
||||
footerIntArrayByDivision,
|
||||
} from "@/components/util/grid/footerSummary";
|
||||
|
||||
const getDivisionFooterLabel = (grid: GridView, divisionIndex: number, withSuffix: boolean) => {
|
||||
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);
|
||||
if (divisionValues.length <= divisionIndex) {
|
||||
return withSuffix ? "합계" : "";
|
||||
}
|
||||
|
||||
const label = divisionValues[divisionIndex] ?? "";
|
||||
return withSuffix ? `${label}합계` : label;
|
||||
};
|
||||
|
||||
const divisionFooterCallbacks = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) },
|
||||
];
|
||||
|
||||
export const SewageExcretaRelatedInfoListRvw = rawExcretaRelatedList.map(tab => ({
|
||||
...tab,
|
||||
columns: tab.columns.map(col => ({
|
||||
...col,
|
||||
editable: false
|
||||
}))
|
||||
}));
|
||||
const divisionFooterLabelOnly = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) },
|
||||
];
|
||||
|
||||
const reviewAddressColumns = [
|
||||
{
|
||||
name: "ctpvNm",
|
||||
fieldName: "ctpvNm",
|
||||
header: { text: "시도" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
footer: divisionFooterCallbacks,
|
||||
},
|
||||
{
|
||||
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: divisionFooterLabelOnly,
|
||||
},
|
||||
];
|
||||
|
||||
const reviewAddressLayout = {
|
||||
name: "areaGroup",
|
||||
header: { text: "행정구역" },
|
||||
items: [
|
||||
{ column: "ctpvNm", footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] },
|
||||
"sggNm",
|
||||
// "admdstNm",
|
||||
],
|
||||
};
|
||||
|
||||
const textCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
});
|
||||
|
||||
const intCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
});
|
||||
|
||||
const decimal1Col = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
});
|
||||
|
||||
// 하수도 통계 검토 12. 하수 및 분뇨 관련 산업
|
||||
const rawExcretaRelatedList = [
|
||||
{
|
||||
tabKey: "ST00120001",
|
||||
tabLabel: "공공기관 하수분야",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewAddressColumns,
|
||||
textCol("ognzNm", "직제"),
|
||||
intCol("empGrssCnt", "직원총수"),
|
||||
intCol("pbadmsNope", "행정직"),
|
||||
intCol("techWholNope", "계"),
|
||||
intCol("engrkCnt", "토목"),
|
||||
intCol("mchnCnt", "기계"),
|
||||
intCol("prvtmCnt", "전기"),
|
||||
intCol("chmengCnt", "화공"),
|
||||
intCol("envCnt", "환경"),
|
||||
intCol("enjbEtcCnt", "기타"),
|
||||
intCol("etcCnt", "기타"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
"ognzNm",
|
||||
"empGrssCnt",
|
||||
"pbadmsNope",
|
||||
{
|
||||
name: "techJobGroup",
|
||||
header: { text: "기술직" },
|
||||
items: [
|
||||
"techWholNope",
|
||||
"engrkCnt",
|
||||
"mchnCnt",
|
||||
"prvtmCnt",
|
||||
"chmengCnt",
|
||||
"envCnt",
|
||||
"enjbEtcCnt",
|
||||
],
|
||||
},
|
||||
"etcCnt",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00120002",
|
||||
tabLabel: "공공기관 분뇨분야",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewAddressColumns,
|
||||
textCol("ognzNm", "직제"),
|
||||
intCol("empGrssCnt", "직원총수"),
|
||||
intCol("pbadmsNope", "행정직"),
|
||||
intCol("techWholNope", "계"),
|
||||
intCol("engrkCnt", "토목"),
|
||||
intCol("mchnCnt", "기계"),
|
||||
intCol("prvtmCnt", "전기"),
|
||||
intCol("chmengCnt", "화공"),
|
||||
intCol("envCnt", "환경"),
|
||||
intCol("enjbEtcCnt", "기타"),
|
||||
intCol("etcCnt", "기타"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
"ognzNm",
|
||||
"empGrssCnt",
|
||||
"pbadmsNope",
|
||||
{
|
||||
name: "techJobGroup",
|
||||
header: { text: "기술직" },
|
||||
items: [
|
||||
"techWholNope",
|
||||
"engrkCnt",
|
||||
"mchnCnt",
|
||||
"prvtmCnt",
|
||||
"chmengCnt",
|
||||
"envCnt",
|
||||
"enjbEtcCnt",
|
||||
],
|
||||
},
|
||||
"etcCnt",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00120003",
|
||||
tabLabel: "분뇨수집·운반업",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewAddressColumns,
|
||||
intCol("bzentyCnt", "업체수(개소)"),
|
||||
intCol("empNope", "종사인원(명)"),
|
||||
intCol("fcltSecrPrstCnt", "계"),
|
||||
intCol("n3TonUdrCnt", "3톤이하"),
|
||||
intCol("n4TonUdrCnt", "4.5톤이하"),
|
||||
intCol("n8TonUdrCnt", "8톤이하"),
|
||||
intCol("etcCnt", "기타"),
|
||||
decimal1Col("clctCnvncPrfmncQnt", "수집·운반실적\n(㎥/년)"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
"bzentyCnt",
|
||||
"empNope",
|
||||
{
|
||||
name: "vehicleGroup",
|
||||
header: { text: "시설(차량) 확보현황(대수)" },
|
||||
items: [
|
||||
"fcltSecrPrstCnt",
|
||||
"n3TonUdrCnt",
|
||||
"n4TonUdrCnt",
|
||||
"n8TonUdrCnt",
|
||||
"etcCnt",
|
||||
],
|
||||
},
|
||||
"clctCnvncPrfmncQnt",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00120004",
|
||||
tabLabel: "개인하수처리관련업",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewAddressColumns,
|
||||
intCol("designMdsrCnt", "업소수"),
|
||||
intCol("designMdsrNope", "종사인원(명)"),
|
||||
intCol("mfbizCnt", "업소수"),
|
||||
intCol("mfbizMdsrNope", "종사인원(명)"),
|
||||
intCol("mngCnt", "업소수"),
|
||||
intCol("mngMdsrNope", "종사인원(명)"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
{
|
||||
name: "constructGroup",
|
||||
header: { text: "개인하수처리시설 설계ㆍ시공업" },
|
||||
items: ["designMdsrCnt", "designMdsrNope"],
|
||||
},
|
||||
{
|
||||
name: "manufactureGroup",
|
||||
header: { text: "개인하수처리시설 제조업" },
|
||||
items: ["mfbizCnt", "mfbizMdsrNope"],
|
||||
},
|
||||
{
|
||||
name: "managementGroup",
|
||||
header: { text: "개인하수처리시설 관리업" },
|
||||
items: ["mngCnt", "mngMdsrNope"],
|
||||
},
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const SewageExcretaRelatedInfoListRvw = rawExcretaRelatedList.map((tab) => ({
|
||||
...tab,
|
||||
columns: tab.columns.map((col) => ({
|
||||
...col,
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
})),
|
||||
}));
|
||||
|
||||
/** @deprecated `SewageExcretaRelatedInfoListRvw` 사용 */
|
||||
export const SewageExcretaRelatedInfoList = SewageExcretaRelatedInfoListRvw;
|
||||
|
||||
@@ -1,199 +1,385 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// 하수도 통계 관리 11. 하수 및 분뇨 찌꺼기(슬러지) 처리
|
||||
import type { GridView } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
footerDecimal1ArrayByDivision,
|
||||
footerIntArrayByDivision,
|
||||
} from "@/components/util/grid/footerSummary";
|
||||
|
||||
const getDivisionFooterLabel = (grid: GridView, divisionIndex: number, withSuffix: boolean) => {
|
||||
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);
|
||||
if (divisionValues.length <= divisionIndex) {
|
||||
return withSuffix ? "합계" : "";
|
||||
}
|
||||
|
||||
const label = divisionValues[divisionIndex] ?? "";
|
||||
return withSuffix ? `${label}합계` : label;
|
||||
};
|
||||
|
||||
const divisionFooterCallbacks = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) },
|
||||
];
|
||||
|
||||
const divisionFooterLabelOnly = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) },
|
||||
];
|
||||
|
||||
const reviewFacilityAddressColumns = [
|
||||
{
|
||||
name: "ctpvNm",
|
||||
fieldName: "ctpvNm",
|
||||
header: { text: "시도" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
footer: divisionFooterCallbacks,
|
||||
},
|
||||
{
|
||||
name: "sggNm",
|
||||
fieldName: "sggNm",
|
||||
header: { text: "시군구" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
},
|
||||
{
|
||||
name: "displayYear",
|
||||
fieldName: "displayYear",
|
||||
header: { text: "구분" },
|
||||
sortdisable: true,
|
||||
footer: divisionFooterLabelOnly,
|
||||
},
|
||||
];
|
||||
|
||||
const reviewFacilityAddressLayout = {
|
||||
name: "areaGroup",
|
||||
header: { text: "행정구역" },
|
||||
items: [
|
||||
{ column: "ctpvNm", footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] },
|
||||
"sggNm",
|
||||
],
|
||||
};
|
||||
|
||||
const textCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
});
|
||||
|
||||
const decimal1Col = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
});
|
||||
|
||||
const intCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
});
|
||||
|
||||
// 하수도 통계 검토 11. 하수 및 분뇨 찌꺼기(슬러지) 처리
|
||||
const rawSludgeList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
tabLabel: '찌꺼기 처리시설 현황',
|
||||
tabKey: "ST00110001",
|
||||
tabLabel: "찌꺼기(슬러지)처리시설현황",
|
||||
rowStartNum: 4,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
/* 기본 정보 */
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'BSC_FCT_NM', fieldName: '하수처리장명', header: { text: '하수\n처리장명' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
|
||||
/* 찌꺼기발생량 */
|
||||
{ name: 'TOT_AMNT', fieldName: '합계(발생)', header: { text: '합계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'P_AMNT', fieldName: '자체(발생)', header: { text: '자체' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'O_AMNT', fieldName: '외부유입량(발생)', header: { text: '외부유입량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 처리 정보 */
|
||||
{ name: 'OUT_FCT_NM', fieldName: '이송처리장', header: { text: '이송처리장' } },
|
||||
{ name: 'OUT_AMNT', fieldName: '이송처리량(㎥/년)', header: { text: '이송처리량\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DSP_MOIRAT', fieldName: '함수율(%,탈수기준)', header: { text: '함수율\n(%,\n탈수기준)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DSP_SM', fieldName: '찌꺼기 처리량 합계(톤/년)', header: { text: '찌꺼기\n처리량\n합계(톤/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 자체 찌꺼기 처리량 */
|
||||
{ name: 'I_1CH_SM', fieldName: '자체찌꺼기 처리량소계', header: { text: '자체찌꺼기\n처리량소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_REC', fieldName: '매립(자체)', header: { text: '매립' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_INC', fieldName: '소각(자체)', header: { text: '소각' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_INC_2CH_SM', fieldName: '소계(소각후)', header: { text: '소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_INC_2CH_REC', fieldName: '매립(소각후)', header: { text: '매립' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_INC_2CH_RAW', fieldName: '제품원료(소각후)', header: { text: '제품원료' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_INC_2CH_COV', fieldName: '복토재(소각후)', header: { text: '복토재' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_INC_2CH_FCT', fieldName: '시설(업체명)_소각후', header: { text: '시설(업체명)' } },
|
||||
{ name: 'I_1CH_SOL', fieldName: '고화(자체)', header: { text: '고화' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_SOL_2CH_SM', fieldName: '소계(고화후)', header: { text: '소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_SOL_2CH_REC', fieldName: '매립(고화후)', header: { text: '매립' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_SOL_2CH_COV', fieldName: '복토재(고화후)', header: { text: '복토재' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_SOL_2CH_FCT', fieldName: '시설(업체명)_고화후', header: { text: '시설(업체명)' } },
|
||||
{ name: 'I_1CH_DRY', fieldName: '건조(자체)', header: { text: '건조' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_DRY_2CH_SM', fieldName: '소계(건조후)', header: { text: '소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_DRY_2CH_REC', fieldName: '매립(건조후)', header: { text: '매립' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_DRY_2CH_INC', fieldName: '소각(건조후)', header: { text: '소각' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_DRY_2CH_FUE', fieldName: '연료(건조후)', header: { text: '연료' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_DRY_2CH_RAW', fieldName: '제품원료(건조후)', header: { text: '제품원료' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_DRY_2CH_COV', fieldName: '복토재(건조후)', header: { text: '복토재' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_DRY_2CH_COM', fieldName: '퇴비화(건조후)', header: { text: '퇴비화' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_DRY_2CH_FCT', fieldName: '시설(업체명)_건조후', header: { text: '시설(업체명)' } },
|
||||
{ name: 'I_1CH_CAR', fieldName: '탄화(자체)', header: { text: '탄화' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_CAR_2CH_SM', fieldName: '소계(탄화후)', header: { text: '소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_CAR_2CH_INC', fieldName: '소각(탄화후)', header: { text: '소각' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_CAR_2CH_FUE', fieldName: '연료(탄화후)', header: { text: '연료' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_CAR_2CH_FCT', fieldName: '시설(업체명)_탄화후', header: { text: '시설(업체명)' } },
|
||||
{ name: 'I_1CH_COM', fieldName: '퇴비화(자체)', header: { text: '퇴비화' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'I_1CH_COM_FCT', fieldName: '시설(업체명)_퇴비화', header: { text: '시설(업체명)' } },
|
||||
|
||||
/* 외부위탁 처리량 */
|
||||
{ name: 'O_CON_SM', fieldName: '소계(외부위탁)', header: { text: '소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'O_CON_REC', fieldName: '매립(외부위탁)', header: { text: '매립' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'O_CON_INC', fieldName: '소각(외부위탁)', header: { text: '소각' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'O_CON_FUE1', fieldName: '연료(외부위탁)', header: { text: '연료' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'O_CON_RAW', fieldName: '제품원료(외부위탁)', header: { text: '제품원료' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'O_CON_COV', fieldName: '복토재(외부위탁)', header: { text: '복토재' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'O_CON_COM', fieldName: '퇴비화(외부위탁)', header: { text: '퇴비화' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'O_CON_FCT_NM', fieldName: '시설(업체명)_외부위탁', header: { text: '시설(업체명)' } },
|
||||
|
||||
/* 기타 */
|
||||
{ name: 'OM_1CH_SM', fieldName: '수도권광역 위탁처리량(톤/년)', header: { text: '수도권광역\n위탁처리량\n(톤/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
...reviewFacilityAddressColumns,
|
||||
textCol("fcltNm", "하수\n처리장명"),
|
||||
decimal1Col("gnamWhol", "합계"),
|
||||
decimal1Col("itslfQnt", "자체"),
|
||||
decimal1Col("otsdInflwQnt", "외부유입량"),
|
||||
textCol("transfTmplntNm", "이송처리장"),
|
||||
decimal1Col("transfPrcsQnt", "이송처리량\n(㎥/년)"),
|
||||
decimal1Col("srpFncRt", "함수율\n(%, 탈수기준)"),
|
||||
decimal1Col("wholSrpPrcsQnt", "찌꺼기\n처리량 합계\n(톤/년)"),
|
||||
decimal1Col("itslfTs1Whol", "자체찌꺼기\n처리량 소계"),
|
||||
decimal1Col("itslfTs1RclmQnt", "매립"),
|
||||
decimal1Col("itslfTs1IncnrQnt", "소각"),
|
||||
decimal1Col("incnrPrcsWhol", "소계"),
|
||||
decimal1Col("incnrPrcsLfvlm", "매립"),
|
||||
decimal1Col("incnrPrcsPrdctRmtrlQnt", "제품원료"),
|
||||
decimal1Col("incnrPrcsFmlyrQnt", "복토재"),
|
||||
textCol("incnrPrcsFcltNm", "시설(업체명)"),
|
||||
decimal1Col("itslfTs1AltdQnt", "고화"),
|
||||
decimal1Col("stbznPrcsWhol", "소계"),
|
||||
decimal1Col("stbznPrcsLfvlm", "매립"),
|
||||
decimal1Col("stbznPrcsFmlyrQnt", "복토재"),
|
||||
textCol("stbznPrcsFcltNm", "시설(업체명)"),
|
||||
decimal1Col("itslfTs1DryQnt", "건조"),
|
||||
decimal1Col("dryPrcsWhol", "소계"),
|
||||
decimal1Col("dryPrcsLfvlm", "매립"),
|
||||
decimal1Col("dryPrcsIncnrQnt", "소각"),
|
||||
decimal1Col("dryPrcsFuelQnt", "연료"),
|
||||
decimal1Col("dryPrcsPrdctRmtrlQnt", "제품원료"),
|
||||
decimal1Col("dryPrcsFmlyrQnt", "복토재"),
|
||||
decimal1Col("dryPrcsCmpstgQnt", "퇴비화"),
|
||||
textCol("dryPrcsFcltNm", "시설(업체명)"),
|
||||
decimal1Col("itslfTs1FuelQnt", "탄화"),
|
||||
decimal1Col("fuelPrcsWhol", "소계"),
|
||||
decimal1Col("fuelPrcsIncnrQnt", "소각"),
|
||||
decimal1Col("fuelPrcsFuelQnt", "연료"),
|
||||
textCol("fuelPrcsFcltNm", "시설(업체명)"),
|
||||
decimal1Col("itslfTs1CmpstgQnt", "퇴비화"),
|
||||
textCol("cmpsPrcsFcltBzentyNm", "시설(업체명)"),
|
||||
decimal1Col("otsdCnsgnWhol", "소계"),
|
||||
decimal1Col("otsdCnsgnRclmQnt", "매립"),
|
||||
decimal1Col("otsdCnsgnIncnrQnt", "소각"),
|
||||
decimal1Col("otsdCnsgnFuelPwplQnt", "연료"),
|
||||
decimal1Col("otsdCnsgnPrdctRmtrlQnt", "제품원료"),
|
||||
decimal1Col("otsdCnsgnFmlyrQnt", "복토재"),
|
||||
decimal1Col("otsdCnsgnCmpstgQnt", "퇴비화"),
|
||||
textCol("otsdCnsgnFcltNm", "시설(업체명)"),
|
||||
decimal1Col("cparTs2Whol", "수도권광역\n위탁처리량\n(톤/년)"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'BSC_FCT_NM', 'YR_GBN',
|
||||
{ name: 'genAmntGroup', header: { text: '찌꺼기발생량(톤/년,탈수기준)' }, items: ['TOT_AMNT', 'P_AMNT', 'O_AMNT'] },
|
||||
'OUT_FCT_NM', 'OUT_AMNT', 'DSP_MOIRAT', 'DSP_SM',
|
||||
reviewFacilityAddressLayout,
|
||||
"displayYear",
|
||||
"fcltNm",
|
||||
{
|
||||
name: 'internalProcessGroup', header: { text: '자체 찌꺼기 처리량(톤/년)' },
|
||||
name: "gnamGroup",
|
||||
header: { text: "찌꺼기발생량(톤/년, 탈수기준)" },
|
||||
items: ["gnamWhol", "itslfQnt", "otsdInflwQnt"],
|
||||
},
|
||||
"transfTmplntNm",
|
||||
"transfPrcsQnt",
|
||||
"srpFncRt",
|
||||
"wholSrpPrcsQnt",
|
||||
{
|
||||
name: "itslfProcessingGroup",
|
||||
header: { text: "자체 찌꺼기 처리량 현황(톤/년)" },
|
||||
items: [
|
||||
'I_1CH_SM', 'I_1CH_REC', 'I_1CH_INC',
|
||||
{ name: 'inc2ndGroup', header: { text: '소각 후 처리(2차)' }, items: ['I_1CH_INC_2CH_SM', 'I_1CH_INC_2CH_REC', 'I_1CH_INC_2CH_RAW', 'I_1CH_INC_2CH_COV', 'I_1CH_INC_2CH_FCT'] },
|
||||
'I_1CH_SOL',
|
||||
{ name: 'sol2ndGroup', header: { text: '고화 후 처리(2차)' }, items: ['I_1CH_SOL_2CH_SM', 'I_1CH_SOL_2CH_REC', 'I_1CH_SOL_2CH_COV', 'I_1CH_SOL_2CH_FCT'] },
|
||||
'I_1CH_DRY',
|
||||
{ name: 'dry2ndGroup', header: { text: '건조 후 처리(2차)' }, items: ['I_1CH_DRY_2CH_SM', 'I_1CH_DRY_2CH_REC', 'I_1CH_DRY_2CH_INC', 'I_1CH_DRY_2CH_FUE', 'I_1CH_DRY_2CH_RAW', 'I_1CH_DRY_2CH_COV', 'I_1CH_DRY_2CH_COM', 'I_1CH_DRY_2CH_FCT'] },
|
||||
'I_1CH_CAR',
|
||||
{ name: 'car2ndGroup', header: { text: '탄화 후 처리(2차)' }, items: ['I_1CH_CAR_2CH_SM', 'I_1CH_CAR_2CH_INC', 'I_1CH_CAR_2CH_FUE', 'I_1CH_CAR_2CH_FCT'] },
|
||||
{ name: 'compostGroup', header: { text: '퇴비화' }, items: ['I_1CH_COM_FCT'] }
|
||||
]
|
||||
"itslfTs1Whol",
|
||||
"itslfTs1RclmQnt",
|
||||
"itslfTs1IncnrQnt",
|
||||
{
|
||||
name: "incnrGroup",
|
||||
header: { text: "소각 후 처리(2차)" },
|
||||
items: [
|
||||
"incnrPrcsWhol",
|
||||
"incnrPrcsLfvlm",
|
||||
"incnrPrcsPrdctRmtrlQnt",
|
||||
"incnrPrcsFmlyrQnt",
|
||||
"incnrPrcsFcltNm",
|
||||
],
|
||||
},
|
||||
"itslfTs1AltdQnt",
|
||||
{
|
||||
name: "stbznGroup",
|
||||
header: { text: "고화 후 처리(2차)" },
|
||||
items: [
|
||||
"stbznPrcsWhol",
|
||||
"stbznPrcsLfvlm",
|
||||
"stbznPrcsFmlyrQnt",
|
||||
"stbznPrcsFcltNm",
|
||||
],
|
||||
},
|
||||
"itslfTs1DryQnt",
|
||||
{
|
||||
name: "dryGroup",
|
||||
header: { text: "건조 후 처리(2차)" },
|
||||
items: [
|
||||
"dryPrcsWhol",
|
||||
"dryPrcsLfvlm",
|
||||
"dryPrcsIncnrQnt",
|
||||
"dryPrcsFuelQnt",
|
||||
"dryPrcsPrdctRmtrlQnt",
|
||||
"dryPrcsFmlyrQnt",
|
||||
"dryPrcsCmpstgQnt",
|
||||
"dryPrcsFcltNm",
|
||||
],
|
||||
},
|
||||
"itslfTs1FuelQnt",
|
||||
{
|
||||
name: "fuelGroup",
|
||||
header: { text: "탄화 후 처리(2차)" },
|
||||
items: [
|
||||
"fuelPrcsWhol",
|
||||
"fuelPrcsIncnrQnt",
|
||||
"fuelPrcsFuelQnt",
|
||||
"fuelPrcsFcltNm",
|
||||
],
|
||||
},
|
||||
"itslfTs1CmpstgQnt",
|
||||
{
|
||||
name: "cmpsGroup",
|
||||
header: { text: "퇴비화" },
|
||||
items: ["cmpsPrcsFcltBzentyNm"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'externalProcessGroup', header: { text: '외부(민간)위탁 처리량(톤/년)' },
|
||||
items: ['O_CON_SM', 'O_CON_REC', 'O_CON_INC', 'O_CON_FUE1', 'O_CON_RAW', 'O_CON_COV', 'O_CON_COM', 'O_CON_FCT_NM']
|
||||
name: "otsdGroup",
|
||||
header: { text: "외부(민간) 위탁 처리량(톤/년)" },
|
||||
items: [
|
||||
"otsdCnsgnWhol",
|
||||
"otsdCnsgnRclmQnt",
|
||||
"otsdCnsgnIncnrQnt",
|
||||
"otsdCnsgnFuelPwplQnt",
|
||||
"otsdCnsgnPrdctRmtrlQnt",
|
||||
"otsdCnsgnFmlyrQnt",
|
||||
"otsdCnsgnCmpstgQnt",
|
||||
"otsdCnsgnFcltNm",
|
||||
],
|
||||
},
|
||||
'OM_1CH_SM', 'RMK'
|
||||
]
|
||||
"cparTs2Whol",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: 'b',
|
||||
tabLabel: '소화조 운영현황',
|
||||
tabKey: "ST00110002",
|
||||
tabLabel: "소화조 운영현황",
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'BSC_FCT_NM', fieldName: '하수처리장명', header: { text: '하수\n처리장명' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'DGSTR_FNNC', fieldName: '소화조 유/무(Y/N)선택', header: { text: '소화조 유/무\n(Y/N)선택' } },
|
||||
{ name: 'DGSTR_CPC', fieldName: '설계용량(㎥)', header: { text: '설계\n용량\n(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_OPR_CPC', fieldName: '실제운전용량(㎥)', header: { text: '실제운전\n용량\n(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_IN', fieldName: '투입량(㎥/일)', header: { text: '투입량\n(㎥/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_TS', fieldName: '투입 TS 농도(%)', header: { text: '투입 TS 농도\n(%)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_YRSTP_RN_DYN', fieldName: '가동일수(연간)', header: { text: '가동일수\n(연간)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_OCCUR_GAS', fieldName: '발생가스량(㎥/일)', header: { text: '발생가스량\n(㎥/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_SM', fieldName: '계(이용현황)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_SLE', fieldName: '판매', header: { text: '판매' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_ELTGNR', fieldName: '발전', header: { text: '발전' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_ONSLF', fieldName: '자체활용', header: { text: '자체활용' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_ETC', fieldName: '기타(CNG등)', header: { text: '기타(CNG등)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DGSTR_GAS', fieldName: '잉여가스량(㎥/일)', header: { text: '잉여가스량\n(㎥/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
...reviewFacilityAddressColumns,
|
||||
textCol("fcltNm", "하수\n처리장명"),
|
||||
textCol("digesterYn", "소화조 유/무\n(Y/N)선택"),
|
||||
intCol("dsgnCpct", "설계\n용량(㎥)"),
|
||||
intCol("actlOperCpct", "실제운전\n용량(㎥)"),
|
||||
decimal1Col("inputQnt", "투입량\n(㎥/일)"),
|
||||
decimal1Col("tsConc", "투입 TS 농도\n(%)"),
|
||||
intCol("oprtngDays", "가동일수\n(연간)"),
|
||||
decimal1Col("gnrtGasQnt", "발생가스량\n(㎥/일)"),
|
||||
decimal1Col("gasTotal", "계"),
|
||||
decimal1Col("gasSale", "판매"),
|
||||
decimal1Col("gasGen", "발전"),
|
||||
decimal1Col("gasSelf", "자체활용"),
|
||||
decimal1Col("gasEtc", "기타(CNG등)"),
|
||||
decimal1Col("surplusGas", "잉여가스량\n(㎥/일)"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'BSC_FCT_NM', 'YR_GBN',
|
||||
reviewFacilityAddressLayout,
|
||||
"displayYear",
|
||||
"fcltNm",
|
||||
{
|
||||
name: 'digesterMainGroup', header: { text: '소화조 운영현황' },
|
||||
name: "operGroup",
|
||||
header: { text: "소화조 운영현황" },
|
||||
items: [
|
||||
'DGSTR_FNNC', 'DGSTR_CPC', 'DGSTR_OPR_CPC', 'DGSTR_IN', 'DGSTR_TS', 'DGSTR_YRSTP_RN_DYN', 'DGSTR_OCCUR_GAS',
|
||||
{ name: 'gasUsageGroup', header: { text: '소화가스 이용현황(㎥/일)' }, items: ['DGSTR_SM', 'DGSTR_SLE', 'DGSTR_ELTGNR', 'DGSTR_ONSLF', 'DGSTR_ETC'] },
|
||||
'DGSTR_GAS'
|
||||
]
|
||||
"digesterYn",
|
||||
"dsgnCpct",
|
||||
"actlOperCpct",
|
||||
"inputQnt",
|
||||
"tsConc",
|
||||
"oprtngDays",
|
||||
"gnrtGasQnt",
|
||||
{
|
||||
name: "gasUsageGroup",
|
||||
header: { text: "소화가스 이용현황(㎥/일)" },
|
||||
items: ["gasTotal", "gasSale", "gasGen", "gasSelf", "gasEtc"],
|
||||
},
|
||||
"surplusGas",
|
||||
],
|
||||
},
|
||||
'RMK'
|
||||
]
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: 'c',
|
||||
tabLabel: '하수찌꺼기 처리시설 운영현황',
|
||||
tabKey: "ST00110003",
|
||||
tabLabel: "하수찌꺼기 처리시설 운영현황",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'SLG_FCT_NM', fieldName: '찌꺼기처리시설명', header: { text: '찌꺼기처리시설명' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'TRT_METHOD', fieldName: '처리방식', header: { text: '처리방식' } },
|
||||
{ name: 'SLG_FCT_CPC', fieldName: '시설용량(톤/일)', header: { text: '시설용량\n(톤/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OPR_CPC', fieldName: '연간처리량(톤/년)', header: { text: '연간처리량\n(톤/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ODY_RN_TIME', fieldName: '일평균 가동시간(1~24)', header: { text: '일평균\n가동시간\n(1~24)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'YRSTP_RN_DYN', fieldName: '연중 가동일(1~330)', header: { text: '연중 가동일\n(1~330)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'OPR_RT', fieldName: '가동율', header: { text: '가동율' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
...reviewFacilityAddressColumns,
|
||||
textCol("fcltNm", "찌꺼기처리\n시설명"),
|
||||
textCol("prcsMthExpln", "처리방식"),
|
||||
decimal1Col("fcltCpct", "시설용량\n(톤/일)"),
|
||||
decimal1Col("anlPrcsQnt", "연간처리량\n(톤/년)"),
|
||||
decimal1Col("dayAvgOprtngHr", "일평균\n가동시간\n(1~24)"),
|
||||
intCol("anlOprtngDayCnt", "연중 가동일\n(1~330)"),
|
||||
decimal1Col("oprtngRt", "가동율(%)"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'SLG_FCT_NM', 'YR_GBN',
|
||||
{ name: 'sludgeFctOprGroup', header: { text: '처리시설 운영현황' }, items: ['TRT_METHOD', 'SLG_FCT_CPC', 'OPR_CPC', 'ODY_RN_TIME', 'YRSTP_RN_DYN', 'OPR_RT'] },
|
||||
'RMK'
|
||||
]
|
||||
reviewFacilityAddressLayout,
|
||||
"displayYear",
|
||||
"fcltNm",
|
||||
{
|
||||
name: "operStatusGroup",
|
||||
header: { text: "처리시설 운영현황" },
|
||||
items: [
|
||||
"prcsMthExpln",
|
||||
"fcltCpct",
|
||||
"anlPrcsQnt",
|
||||
"dayAvgOprtngHr",
|
||||
"anlOprtngDayCnt",
|
||||
"oprtngRt",
|
||||
],
|
||||
},
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: 'd',
|
||||
tabLabel: '분뇨찌꺼기 처리현황',
|
||||
tabKey: "ST00110004",
|
||||
tabLabel: "분뇨찌꺼기(슬러지)처리현황",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'ECSCS_NM', fieldName: '분뇨 처리장명', header: { text: '분 뇨\n처리장명' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'OCTY', fieldName: '발생량(A)=(B)+(C)(㎥/년)', header: { text: '발생량\n(A)=(B)+(C)\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'PROCESS_CPCTY_SM', fieldName: '계(B)', header: { text: '계(B)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RUSE', fieldName: '재활용', header: { text: '재활용' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'LAND_RCLM', fieldName: '육상매립', header: { text: '육상매립' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'INCNR', fieldName: '소각', header: { text: '소각' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OCEAN_DPSL', fieldName: '해양투기', header: { text: '해양투기' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'FUEL', fieldName: '연료화', header: { text: '연료화' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ETC', fieldName: '기타', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'CYFD_CPCTY', fieldName: '미처분량(이월량)(C)(㎥/년)', header: { text: '미처분량\n(이월량)\n(C)\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OUT_BSC_FCT_NM', fieldName: '타시설 연계여부', header: { text: '타시설\n연계여부' } },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
{ name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' } },
|
||||
{ name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' } },
|
||||
...reviewFacilityAddressColumns,
|
||||
textCol("fcltNm", "분뇨\n처리장명"),
|
||||
decimal1Col("gnam", "발생량\n(A)=(B)+(C)"),
|
||||
decimal1Col("wholDspsQnt", "계(B)"),
|
||||
decimal1Col("rcycQnt", "재활용"),
|
||||
decimal1Col("olandLfvlm", "육상매립"),
|
||||
decimal1Col("incnrQnt", "소각"),
|
||||
decimal1Col("oceanInputQnt", "해양투기"),
|
||||
decimal1Col("fuelQnt", "연료화"),
|
||||
decimal1Col("etcQnt", "기타"),
|
||||
decimal1Col("nondspsQnt", "미처분량\n(이월량)(C)"),
|
||||
textCol("swtrmtFcltNm", "타시설\n연계여부"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'ECSCS_NM', 'YR_GBN', 'OCTY',
|
||||
{ name: 'disposalAmntGroup', header: { text: '처 분 량(㎥/년)' }, items: ['PROCESS_CPCTY_SM', 'RUSE', 'LAND_RCLM', 'INCNR', 'OCEAN_DPSL', 'FUEL', 'ETC'] },
|
||||
'CYFD_CPCTY', 'OUT_BSC_FCT_NM', 'RMK'
|
||||
]
|
||||
}
|
||||
reviewFacilityAddressLayout,
|
||||
"displayYear",
|
||||
"fcltNm",
|
||||
"gnam",
|
||||
{
|
||||
name: "dspsGroup",
|
||||
header: { text: "처분량" },
|
||||
items: [
|
||||
"wholDspsQnt",
|
||||
"rcycQnt",
|
||||
"olandLfvlm",
|
||||
"incnrQnt",
|
||||
"oceanInputQnt",
|
||||
"fuelQnt",
|
||||
"etcQnt",
|
||||
],
|
||||
},
|
||||
"nondspsQnt",
|
||||
"swtrmtFcltNm",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const SewageExcretaSludgeInfoListRvw = rawSludgeList.map(tab => ({
|
||||
export const SewageExcretaSludgeInfoListRvw = rawSludgeList.map((tab) => ({
|
||||
...tab,
|
||||
columns: tab.columns.map(col => ({
|
||||
columns: tab.columns.map((col) => ({
|
||||
...col,
|
||||
editable: false
|
||||
}))
|
||||
}));
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
})),
|
||||
}));
|
||||
|
||||
/** @deprecated `SewageExcretaSludgeInfoListRvw` 사용 */
|
||||
export const SewageExcretaSludgeInfoList = SewageExcretaSludgeInfoListRvw;
|
||||
|
||||
@@ -1,215 +1,369 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// 하수도 통계 관리 09. 하수도 재정
|
||||
const rawFinanceList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
tabLabel: '하수도재정 결산',
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 1,
|
||||
columns: [
|
||||
/* 1. 기본 정보 */
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'ADST_CD', fieldName: '행정코드', header: { text: '행정코드' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
import type { GridView } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
footerAvgDecimalArrayByDivision,
|
||||
footerDecimal1ArrayByDivision,
|
||||
footerIntArrayByDivision,
|
||||
} from "@/components/util/grid/footerSummary";
|
||||
|
||||
/* 2. 재정별 사업비 (수입) */
|
||||
{ name: 'FNANC_WCT_SM', fieldName: '계(수입)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'NTNXPN', fieldName: '중앙정부 지원액', header: { text: '중앙정부\n지원액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'LCLEXPNS', fieldName: '특․광역시, 도', header: { text: '특․광역시, 도' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'LCLTY_CT', fieldName: '시,군,구 지자체', header: { text: '시,군,구 지자체' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SEWER_RNTFEE', fieldName: '하수도 사용료', header: { text: '하수도\n사용료' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'AMNTBRNCSR', fieldName: '원인자 부담금', header: { text: '원인자\n부담금' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ETC_INCME', fieldName: '기타 잡수입', header: { text: '기타\n잡수입' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'CYFD_AMOUNT', fieldName: '이월액', header: { text: '이월액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 3. 부채액 */
|
||||
{ name: 'DEBT_AMOUNT', fieldName: '부채액(백만원)', header: { text: '부채액\n(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
const getDivisionFooterLabel = (grid: GridView, divisionIndex: number, withSuffix: boolean) => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const uniqueValues = new Set<string>();
|
||||
|
||||
/* 4. 용도별 사업비 (지출) */
|
||||
{ name: 'PRPOS_WCT_SM', fieldName: '계(지출)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'PRCPLT_SM', fieldName: '소계(처리장)', header: { text: '소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'PRCPLT_FCLTY', fieldName: '시설비(처리장)', header: { text: '시설비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'PRCPLT_RPAIR', fieldName: '개보수비(처리장)', header: { text: '개보수비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'PRCPLT_ORPNS', fieldName: '운영 유지비(처리장)', header: { text: '운영\n유지비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'PRCPLT_ETC', fieldName: '기타(처리장)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SEWAGE_SM', fieldName: '소계(관로)', header: { text: '소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SEWAGE_FCLTY', fieldName: '시설비(관로)', header: { text: '시설비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SEWAGE_RPAIR', fieldName: '개보수비(관로)', header: { text: '개보수비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SEWAGE_ORPNS', fieldName: '운영 유지비(관로)', header: { text: '운영\n유지비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SEWAGE_ETC', fieldName: '기타(관로)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SWGE_STRGE_SM', fieldName: '소계(저류시설)', header: { text: '소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SWGE_STRGE_FCLTY', fieldName: '시설비(저류시설)', header: { text: '시설비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SWGE_STRGE_RPAIR', fieldName: '개보수비(저류시설)', header: { text: '개보수비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SWGE_STRGE_ORPNS', fieldName: '운영 유지비(저류시설)', header: { text: '운영\n유지비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SWGE_STRGE_ETC', fieldName: '기타(저류시설)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ETC_SM', fieldName: '소계(기타지출)', header: { text: '소계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ACDNT_CYFD_AMOUNT', fieldName: '사고 이월액', header: { text: '사고\n이월액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'UNRCIV_AMOUNT', fieldName: '미수납액', header: { text: '미수납액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ETC_AMOUNT', fieldName: '기타(지출)', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 5. 비고 */
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'ADST_CD',
|
||||
{
|
||||
name: 'financeGroup',
|
||||
header: { text: '재정별 사업비(백만원)' },
|
||||
items: [
|
||||
'FNANC_WCT_SM', 'NTNXPN',
|
||||
{ name: 'localGroup', header: { text: '지방비' }, items: ['LCLEXPNS', 'LCLTY_CT'] },
|
||||
'SEWER_RNTFEE', 'AMNTBRNCSR', 'ETC_INCME', 'CYFD_AMOUNT'
|
||||
]
|
||||
},
|
||||
'DEBT_AMOUNT',
|
||||
{
|
||||
name: 'usageGroup',
|
||||
header: { text: '용도별사업비(백만원)' },
|
||||
items: [
|
||||
'PRPOS_WCT_SM',
|
||||
{ name: 'plantGroup', header: { text: '처리장' }, items: ['PRCPLT_SM', 'PRCPLT_FCLTY', 'PRCPLT_RPAIR', 'PRCPLT_ORPNS', 'PRCPLT_ETC'] },
|
||||
{ name: 'pipeGroup', header: { text: '하수관로' }, items: ['SEWAGE_SM', 'SEWAGE_FCLTY', 'SEWAGE_RPAIR', 'SEWAGE_ORPNS', 'SEWAGE_ETC'] },
|
||||
{ name: 'storageGroup', header: { text: '하수저류시설' }, items: ['SWGE_STRGE_SM', 'SWGE_STRGE_FCLTY', 'SWGE_STRGE_RPAIR', 'SWGE_STRGE_ORPNS', 'SWGE_STRGE_ETC'] },
|
||||
{ name: 'etcUsageGroup', header: { text: '기타' }, items: ['ETC_SM', 'ACDNT_CYFD_AMOUNT', 'UNRCIV_AMOUNT', 'ETC_AMOUNT'] }
|
||||
]
|
||||
},
|
||||
'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'b',
|
||||
tabLabel: '하수도 요금',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 1,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'FYER_LEVY_QY', fieldName: '연간조정량(㎥)(A)', header: { text: '연간조정량\n(㎥)\n(A)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'LEVY_AMOUNT', fieldName: '하수도사용료수익(원)(B)', header: { text: '하수도사용료수익\n(원)\n(B)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'AVRG_UNTPC', fieldName: '㎥당요금(원/㎥) C=(B/A)', header: { text: '㎥당요금(원/㎥)\nC=(B/A)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SMRIZE_PRMPC', fieldName: '총괄원가(원)(D)', header: { text: '총괄원가(원)\n(D)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SMRIZE_UNIT_PRMPC', fieldName: '㎥당원가(원/㎥) E=(D/A)', header: { text: '㎥당원가(원/㎥)\nE=(D/A)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RLTY_RT', fieldName: '요금현실화율(%) F=(C/E*100)', header: { text: '요금현실화율(%)\nF=(C/E*100)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } }
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'FYER_LEVY_QY', 'LEVY_AMOUNT', 'AVRG_UNTPC', 'SMRIZE_PRMPC', 'SMRIZE_UNIT_PRMPC', 'RLTY_RT', 'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'c',
|
||||
tabLabel: '하수도 사용료',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 1,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'LEVY_AMOUNT', fieldName: '부과액(총계)', header: { text: '부과액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'LEVY_QY', fieldName: '부과량(총계)', header: { text: '부과량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'HOME_LEVY_AMOUNT', fieldName: '부과액(가정용)', header: { text: '부과액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'HOME_LEVY_QY', fieldName: '부과량(가정용)', header: { text: '부과량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'HOME_UNTPC', fieldName: '단가(가정용)', header: { text: '단가' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'JOB_LEVY_AMOUNT', fieldName: '부과액(업무용)', header: { text: '부과액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'JOB_LEVY_QY', fieldName: '부과량(업무용)', header: { text: '부과량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'JOB_UNTPC', fieldName: '단가(업무용)', header: { text: '단가' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'BSN_LEVY_AMOUNT', fieldName: '부과액(영업용)', header: { text: '부과액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'BSN_LEVY_QY', fieldName: '부과량(영업용)', header: { text: '부과량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'BSN_UNTPC', fieldName: '단가(영업용)', header: { text: '단가' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'BATHS1_LEVY_AMOUNT', fieldName: '부과액(욕탕1)', header: { text: '부과액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'BATHS1_LEVY_QY', fieldName: '부과량(욕탕1)', header: { text: '부과량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'BATHS1_UNTPC', fieldName: '단가(욕탕1)', header: { text: '단가' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'BATHS2_LEVY_AMOUNT', fieldName: '부과액(욕탕2)', header: { text: '부과액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'BATHS2_LEVY_QY', fieldName: '부과량(욕탕2)', header: { text: '부과량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'BATHS2_UNTPC', fieldName: '단가(욕탕2)', header: { text: '단가' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'INDUST_LEVY_AMOUNT', fieldName: '부과액(산업용)', header: { text: '부과액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'INDUST_LEVY_QY', fieldName: '부과량(산업용)', header: { text: '부과량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'INDUST_UNTPC', fieldName: '단가(산업용)', header: { text: '단가' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ETC_LEVY_AMOUNT', fieldName: '부과액(기타사용료)', header: { text: '부과액' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ETC_LEVY_QY', fieldName: '부과량(기타사용료)', header: { text: '부과량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ETC_UNTPC', fieldName: '단가(기타사용료)', header: { text: '단가' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
{
|
||||
name: 'totalGroup', header: { text: '총계' },
|
||||
items: [{ name: 'tAmtUnit', header: { text: '부과액\n(백만원)' }, items: ['LEVY_AMOUNT'] }, { name: 'tQyUnit', header: { text: '부과량\n(천톤)' }, items: ['LEVY_QY'] }]
|
||||
},
|
||||
{
|
||||
name: 'homeGroup', header: { text: '가 정 용' },
|
||||
items: [{ name: 'hAmtUnit', header: { text: '부과액\n(백만원)' }, items: ['HOME_LEVY_AMOUNT'] }, { name: 'hQyUnit', header: { text: '부과량\n(천톤)' }, items: ['HOME_LEVY_QY'] }, { name: 'hUntUnit', header: { text: '단가\n(원/톤)' }, items: ['HOME_UNTPC'] }]
|
||||
},
|
||||
{
|
||||
name: 'jobGroup', header: { text: '업 무 용' },
|
||||
items: [{ name: 'jAmtUnit', header: { text: '부과액\n(백만원)' }, items: ['JOB_LEVY_AMOUNT'] }, { name: 'jQyUnit', header: { text: '부과량\n(천톤)' }, items: ['JOB_LEVY_QY'] }, { name: 'jUntUnit', header: { text: '단가\n(원/톤)' }, items: ['JOB_UNTPC'] }]
|
||||
},
|
||||
{
|
||||
name: 'bsnGroup', header: { text: '영 업 용(일 반 용)' },
|
||||
items: [{ name: 'bAmtUnit', header: { text: '부과액\n(백만원)' }, items: ['BSN_LEVY_AMOUNT'] }, { name: 'bQyUnit', header: { text: '부과량\n(천톤)' }, items: ['BSN_LEVY_QY'] }, { name: 'bUntUnit', header: { text: '단가\n(원/톤)' }, items: ['BSN_UNTPC'] }]
|
||||
},
|
||||
{
|
||||
name: 'bath1Group', header: { text: '욕 탕 1종(대중탕용)' },
|
||||
items: [{ name: 'bt1AmtUnit', header: { text: '부과액\n(백만원)' }, items: ['BATHS1_LEVY_AMOUNT'] }, { name: 'bt1QyUnit', header: { text: '부과량\n(천톤)' }, items: ['BATHS1_LEVY_QY'] }, { name: 'bt1UntUnit', header: { text: '단가\n(원/톤)' }, items: ['BATHS1_UNTPC'] }]
|
||||
},
|
||||
{
|
||||
name: 'bath2Group', header: { text: '욕 탕 2종' },
|
||||
items: [{ name: 'bt2AmtUnit', header: { text: '부과액\n(백만원)' }, items: ['BATHS2_LEVY_AMOUNT'] }, { name: 'bt2QyUnit', header: { text: '부과량\n(천톤)' }, items: ['BATHS2_LEVY_QY'] }, { name: 'bt2UntUnit', header: { text: '단가\n(원/톤)' }, items: ['BATHS2_UNTPC'] }]
|
||||
},
|
||||
{
|
||||
name: 'industGroup', header: { text: '산업용 및 공업용' },
|
||||
items: [{ name: 'iAmtUnit', header: { text: '부과액\n(백만원)' }, items: ['INDUST_LEVY_AMOUNT'] }, { name: 'iQyUnit', header: { text: '부과량\n(천톤)' }, items: ['INDUST_LEVY_QY'] }, { name: 'iUntUnit', header: { text: '단가\n(원/톤)' }, items: ['INDUST_UNTPC'] }]
|
||||
},
|
||||
{
|
||||
name: 'etcGroup', header: { text: '기타' },
|
||||
items: [{ name: 'eAmtUnit', header: { text: '부과액\n(백만원)' }, items: ['ETC_LEVY_AMOUNT'] }, { name: 'eQyUnit', header: { text: '부과량\n(천톤)' }, items: ['ETC_LEVY_QY'] }, { name: 'eUntUnit', header: { text: '단가\n(원/톤)' }, items: ['ETC_UNTPC'] }]
|
||||
},
|
||||
'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'd',
|
||||
tabLabel: '원인자부담금',
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'LEVY_CO', fieldName: '건수(부과액)', header: { text: '건수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'LEVY_AMOUNT', fieldName: '금액(백만원)(부과액)', header: { text: '금액(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'TRGET_LEV_STTUS_CO', fieldName: '건수(계)', header: { text: '건수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'TRGET_LEV_STTUS_AMOUNT', fieldName: '금액(백만원)(계)', header: { text: '금액(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DRNG_EQP_CO', fieldName: '건수(배수설비)', header: { text: '건수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'DRNG_EQP_AMOUNT', fieldName: '금액(백만원)(배수설비)', header: { text: '금액(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'CNTRWK_CO', fieldName: '건수(타공사)', header: { text: '건수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CNTRWK_AMOUNT', fieldName: '금액(백만원)(타공사)', header: { text: '금액(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN',
|
||||
{ name: 'levyMainGroup', header: { text: '부과액' }, items: ['LEVY_CO', 'LEVY_AMOUNT'] },
|
||||
{
|
||||
name: 'collectGroup', header: { text: '대상별 징수현황' },
|
||||
items: [
|
||||
{ name: 'collectTotal', header: { text: '계' }, items: ['TRGET_LEV_STTUS_CO', 'TRGET_LEV_STTUS_AMOUNT'] },
|
||||
{ name: 'contractorGroup', header: { text: '배수설비설치자\n(하수도법 제61조 제1항)' }, items: ['DRNG_EQP_CO', 'DRNG_EQP_AMOUNT'] },
|
||||
{ name: 'otherGroup', header: { text: '타공사·타행위자\n(하수도법 제61조 제2항)' }, items: ['CNTRWK_CO', 'CNTRWK_AMOUNT'] }
|
||||
]
|
||||
},
|
||||
'RMK'
|
||||
]
|
||||
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 withSuffix ? "합계" : "";
|
||||
}
|
||||
|
||||
const label = divisionValues[divisionIndex] ?? "";
|
||||
return withSuffix ? `${label}합계` : label;
|
||||
};
|
||||
|
||||
const divisionFooterCallbacks = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) },
|
||||
];
|
||||
|
||||
export const SewerageFinanceInfoListRvw = rawFinanceList.map(tab => ({
|
||||
const divisionFooterLabelOnly = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) },
|
||||
];
|
||||
|
||||
const reviewAddressColumns = [
|
||||
{
|
||||
name: "ctpvNm",
|
||||
fieldName: "ctpvNm",
|
||||
header: { text: "시도" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
footer: divisionFooterCallbacks,
|
||||
},
|
||||
{
|
||||
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: divisionFooterLabelOnly,
|
||||
},
|
||||
];
|
||||
|
||||
const reviewAddressLayout = {
|
||||
name: "areaGroup",
|
||||
header: { text: "행정구역" },
|
||||
items: [
|
||||
{ column: "ctpvNm", footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] },
|
||||
"sggNm",
|
||||
"admdstNm",
|
||||
],
|
||||
};
|
||||
|
||||
const decimal1Col = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
});
|
||||
|
||||
const intCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
});
|
||||
|
||||
// 하수도 통계 검토 09. 하수도 재정
|
||||
const rawFinanceList = [
|
||||
{
|
||||
tabKey: "ST00090001",
|
||||
tabLabel: "하수도재정 결산",
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewAddressColumns,
|
||||
decimal1Col("fnnByWholPjtcoAmt", "계"),
|
||||
decimal1Col("cnterGovSprtAmt", "중앙정부\n지원액"),
|
||||
decimal1Col("mctpvLcexpAmt", "특․광역시, 도"),
|
||||
decimal1Col("wareaSggLclAmt", "시,군,구 지자체"),
|
||||
decimal1Col("sewerUseAmt", "하수도\n사용료"),
|
||||
decimal1Col("orgprBrdnAmt", "원인자\n부담금"),
|
||||
decimal1Col("etcIncmAmt", "기타\n잡수입"),
|
||||
decimal1Col("crfwAmt", "이월액"),
|
||||
decimal1Col("lbltAmt", "부채액\n(백만원)"),
|
||||
decimal1Col("usgByWholPjtcoAmt", "계"),
|
||||
decimal1Col("tmplntWholAmt", "소계"),
|
||||
decimal1Col("tmplntFcltAmt", "시설비"),
|
||||
decimal1Col("tmplntMendngAmt", "개보수비"),
|
||||
decimal1Col("tmplntOperKeepAmt", "운영\n유지비"),
|
||||
decimal1Col("tmplntEtcAmt", "기타"),
|
||||
decimal1Col("wholSwagePplAmt", "소계"),
|
||||
decimal1Col("swagePplFcltAmt", "시설비"),
|
||||
decimal1Col("swagePplMendngAmt", "개보수비"),
|
||||
decimal1Col("swagePplOperKeepAmt", "운영\n유지비"),
|
||||
decimal1Col("swagePplEtcAmt", "기타"),
|
||||
decimal1Col("wholSwageImpdFcltAmt", "소계"),
|
||||
decimal1Col("swageImpdFcltAmt", "시설비"),
|
||||
decimal1Col("swageImpdMendngAmt", "개보수비"),
|
||||
decimal1Col("swageImpdOperKeepAmt", "운영\n유지비"),
|
||||
decimal1Col("swageImpdFcltEtcAmt", "기타"),
|
||||
decimal1Col("etcWholAmt", "소계"),
|
||||
decimal1Col("etcAcdntCrfwAmt", "사고\n이월액"),
|
||||
decimal1Col("etcNrcpAmt", "미수납액"),
|
||||
decimal1Col("etcAmt", "기타"),
|
||||
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
|
||||
],
|
||||
columnLayout: [
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
{
|
||||
name: "financeGroup",
|
||||
header: { text: "재정별 사업비(백만원)" },
|
||||
items: [
|
||||
"fnnByWholPjtcoAmt",
|
||||
"cnterGovSprtAmt",
|
||||
{
|
||||
name: "localGroup",
|
||||
header: { text: "지방비" },
|
||||
items: ["mctpvLcexpAmt", "wareaSggLclAmt"],
|
||||
},
|
||||
"sewerUseAmt",
|
||||
"orgprBrdnAmt",
|
||||
"etcIncmAmt",
|
||||
"crfwAmt",
|
||||
],
|
||||
},
|
||||
"lbltAmt",
|
||||
{
|
||||
name: "purposeGroup",
|
||||
header: { text: "용도별사업비(백만원)" },
|
||||
items: [
|
||||
"usgByWholPjtcoAmt",
|
||||
{
|
||||
name: "t1Group",
|
||||
header: { text: "처리장" },
|
||||
items: [
|
||||
"tmplntWholAmt",
|
||||
"tmplntFcltAmt",
|
||||
"tmplntMendngAmt",
|
||||
"tmplntOperKeepAmt",
|
||||
"tmplntEtcAmt",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "t2Group",
|
||||
header: { text: "하수관로" },
|
||||
items: [
|
||||
"wholSwagePplAmt",
|
||||
"swagePplFcltAmt",
|
||||
"swagePplMendngAmt",
|
||||
"swagePplOperKeepAmt",
|
||||
"swagePplEtcAmt",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "t3Group",
|
||||
header: { text: "유수지 및 배수펌프장" },
|
||||
items: [
|
||||
"wholSwageImpdFcltAmt",
|
||||
"swageImpdFcltAmt",
|
||||
"swageImpdMendngAmt",
|
||||
"swageImpdOperKeepAmt",
|
||||
"swageImpdFcltEtcAmt",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "t4Group",
|
||||
header: { text: "기타" },
|
||||
items: ["etcWholAmt", "etcAcdntCrfwAmt", "etcNrcpAmt", "etcAmt"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00090002",
|
||||
tabLabel: "하수도 요금",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewAddressColumns,
|
||||
intCol("anlLevyQnt", "연간조정량(㎡)"),
|
||||
intCol("levyAmt", "하수도사용료수익(원)"),
|
||||
decimal1Col("avgUntprcAmt", "㎡당 요금(원/㎡)"),
|
||||
intCol("grssPrmcoAmt", "총관원가(원)"),
|
||||
decimal1Col("grssUnitPrmcoAmt", "㎡당원가(원/㎡)"),
|
||||
{
|
||||
name: "rlztnRt",
|
||||
fieldName: "rlztnRt",
|
||||
header: { text: "현실화율(%)" },
|
||||
footer: footerAvgDecimalArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
},
|
||||
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
|
||||
],
|
||||
columnLayout: [
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
"anlLevyQnt",
|
||||
"levyAmt",
|
||||
"avgUntprcAmt",
|
||||
"grssPrmcoAmt",
|
||||
"grssUnitPrmcoAmt",
|
||||
"rlztnRt",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00090003",
|
||||
tabLabel: "하수도 사용료",
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewAddressColumns,
|
||||
decimal1Col("levyAmt", "부과액(백만원)"),
|
||||
decimal1Col("levyQnt", "부과량(천톤)"),
|
||||
decimal1Col("hshusLevyAmt", "부과액"),
|
||||
decimal1Col("hshusLevyQnt", "부과량"),
|
||||
intCol("hshusUntprcAmt", "단가"),
|
||||
decimal1Col("taskFrLevyAmt", "부과액"),
|
||||
decimal1Col("taskFrLevyQnt", "부과량"),
|
||||
intCol("taskFrUntprcAmt", "단가"),
|
||||
decimal1Col("bsnGnrlLevyAmt", "부과액(백만원)"),
|
||||
decimal1Col("bsnGnrlLevyQnt", "부과량(천톤)"),
|
||||
intCol("bsnGnrlUntprcAmt", "단가(원/톤)"),
|
||||
decimal1Col("massBthhsFrLevyAmt", "부과액(백만원)"),
|
||||
decimal1Col("massBthhsFrLevyQnt", "부과량(천톤)"),
|
||||
intCol("massBthhsFrUntprcAmt", "단가(원/톤)"),
|
||||
decimal1Col("bthhsFrSpcs2LevyAmt", "부과액"),
|
||||
decimal1Col("bthhsFrSpcs2LevyQnt", "부과량"),
|
||||
intCol("bthhsFrSpcs2UntprcAmt", "단가"),
|
||||
decimal1Col("induMdsrLevyAmt", "부과액(백만원)"),
|
||||
decimal1Col("induMdsrLevyQnt", "부과량(천톤)"),
|
||||
intCol("induMdsrUntprcAmt", "단가(원/톤)"),
|
||||
decimal1Col("etcLevyAmt", "부과액"),
|
||||
decimal1Col("etcLevyQnt", "부과량"),
|
||||
intCol("etcLevyUntprcAmt", "단가"),
|
||||
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
|
||||
],
|
||||
columnLayout: [
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
{ name: "totalGroup", header: { text: "총계" }, items: ["levyAmt", "levyQnt"] },
|
||||
{
|
||||
name: "g1Group",
|
||||
header: { text: "가정용" },
|
||||
items: ["hshusLevyAmt", "hshusLevyQnt", "hshusUntprcAmt"],
|
||||
},
|
||||
{
|
||||
name: "g2Group",
|
||||
header: { text: "업무용" },
|
||||
items: ["taskFrLevyAmt", "taskFrLevyQnt", "taskFrUntprcAmt"],
|
||||
},
|
||||
{
|
||||
name: "g3Group",
|
||||
header: { text: "영업용(일반용)" },
|
||||
items: ["bsnGnrlLevyAmt", "bsnGnrlLevyQnt", "bsnGnrlUntprcAmt"],
|
||||
},
|
||||
{
|
||||
name: "g4Group",
|
||||
header: { text: "욕탕 1종(대중탕용)" },
|
||||
items: ["massBthhsFrLevyAmt", "massBthhsFrLevyQnt", "massBthhsFrUntprcAmt"],
|
||||
},
|
||||
{
|
||||
name: "g5Group",
|
||||
header: { text: "욕탕2종" },
|
||||
items: ["bthhsFrSpcs2LevyAmt", "bthhsFrSpcs2LevyQnt", "bthhsFrSpcs2UntprcAmt"],
|
||||
},
|
||||
{
|
||||
name: "g6Group",
|
||||
header: { text: "산업 및 공업용" },
|
||||
items: ["induMdsrLevyAmt", "induMdsrLevyQnt", "induMdsrUntprcAmt"],
|
||||
},
|
||||
{
|
||||
name: "g7Group",
|
||||
header: { text: "기타" },
|
||||
items: ["etcLevyAmt", "etcLevyQnt", "etcLevyUntprcAmt"],
|
||||
},
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00090004",
|
||||
tabLabel: "원인자부담금",
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewAddressColumns,
|
||||
intCol("levyEvntcsCnt", "건수"),
|
||||
decimal1Col("levyAmt", "금액(백만원)"),
|
||||
intCol("trgtByWholClctnEvntcsCnt", "건수"),
|
||||
decimal1Col("trgtByWholClctnAmt", "금액(백만원)"),
|
||||
intCol("mltvEqpmEvntcsCnt", "건수"),
|
||||
decimal1Col("mltvEqpmAmt", "금액(백만원)"),
|
||||
intCol("othrCstrnOthrActrCnt", "건수"),
|
||||
decimal1Col("othrCstrnOthrActrAmt", "금액(백만원)"),
|
||||
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
|
||||
],
|
||||
columnLayout: [
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
{ name: "levyGroup", header: { text: "부과액" }, items: ["levyEvntcsCnt", "levyAmt"] },
|
||||
{
|
||||
name: "collectionStatusGroup",
|
||||
header: { text: "대상별 징수현황" },
|
||||
items: [
|
||||
{
|
||||
name: "c1Group",
|
||||
header: { text: "계" },
|
||||
items: ["trgtByWholClctnEvntcsCnt", "trgtByWholClctnAmt"],
|
||||
},
|
||||
{
|
||||
name: "c2Group",
|
||||
header: { text: "배수설비설치자\n(하수도법 제61조 제1항)" },
|
||||
items: ["mltvEqpmEvntcsCnt", "mltvEqpmAmt"],
|
||||
},
|
||||
{
|
||||
name: "c3Group",
|
||||
header: { text: "타공사·타행위자\n(하수도법 제61조 제2항)" },
|
||||
items: ["othrCstrnOthrActrCnt", "othrCstrnOthrActrAmt"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const SewerageFinanceInfoListRvw = rawFinanceList.map((tab) => ({
|
||||
...tab,
|
||||
columns: tab.columns.map(col => ({
|
||||
columns: tab.columns.map((col) => ({
|
||||
...col,
|
||||
editable: false
|
||||
}))
|
||||
}));
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -1,117 +1,213 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// 하수도 통계 관리 13. 하수도 서비스 수준
|
||||
import type { GridView } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
footerAvgDecimalArrayByDivision,
|
||||
footerDecimal1ArrayByDivision,
|
||||
footerIntArrayByDivision,
|
||||
} from "@/components/util/grid/footerSummary";
|
||||
|
||||
const getDivisionFooterLabel = (grid: GridView, divisionIndex: number, withSuffix: boolean) => {
|
||||
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);
|
||||
if (divisionValues.length <= divisionIndex) {
|
||||
return withSuffix ? "합계" : "";
|
||||
}
|
||||
|
||||
const label = divisionValues[divisionIndex] ?? "";
|
||||
return withSuffix ? `${label}합계` : label;
|
||||
};
|
||||
|
||||
const divisionFooterCallbacks = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) },
|
||||
];
|
||||
|
||||
const divisionFooterLabelOnly = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) },
|
||||
];
|
||||
|
||||
const reviewAddressColumns = [
|
||||
{
|
||||
name: "ctpvNm",
|
||||
fieldName: "ctpvNm",
|
||||
header: { text: "시도" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
footer: divisionFooterCallbacks,
|
||||
},
|
||||
{
|
||||
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: divisionFooterLabelOnly,
|
||||
},
|
||||
];
|
||||
|
||||
const reviewAddressLayout = {
|
||||
name: "areaGroup",
|
||||
header: { text: "행정구역" },
|
||||
items: [
|
||||
{ column: "ctpvNm", footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] },
|
||||
"sggNm",
|
||||
// "admdstNm",
|
||||
],
|
||||
};
|
||||
|
||||
const textCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
});
|
||||
|
||||
const intCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
});
|
||||
|
||||
const decimal1Col = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
});
|
||||
|
||||
const avgDecimalCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerAvgDecimalArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
});
|
||||
|
||||
// 하수도 통계 검토 13. 하수도 서비스 수준
|
||||
const rawServiceLevelList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
tabLabel: '민원',
|
||||
tabKey: "ST00130001",
|
||||
tabLabel: "민원",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
/* 1. 기본 정보 */
|
||||
{ name: 'SIDO', fieldName: 'SIDO', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: 'GUGUN', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: 'YR_GBN', header: { text: '구분' } },
|
||||
|
||||
/* 2. 하수처리 인구 (정수 반영) */
|
||||
{ name: 'POPLTN_SM', fieldName: 'POPLTN_SM', header: { text: '하수처리인구\n(명)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
|
||||
/* 3. 민원유형 (단위: 건 / 정수 반영) */
|
||||
{ name: 'CVPL_CM', fieldName: 'CVPL_CM', header: { text: '총계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_DCWTR_QLTWTR', fieldName: 'CVPL_DCWTR_QLTWTR', header: { text: '악취' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_BSML', fieldName: 'CVPL_BSML', header: { text: '하수관로' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_FIVW', fieldName: 'CVPL_FIVW', header: { text: '배수설비' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_POLLUTN_ACDNT', fieldName: 'CVPL_POLLUTN_ACDNT', header: { text: '맨홀' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_SSOS', fieldName: 'CVPL_SSOS', header: { text: '정화조' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_CHRGE', fieldName: 'CVPL_CHRGE', header: { text: '하수도요금' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CVPL_ETC', fieldName: 'CVPL_ETC', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
|
||||
/* 4. 성과 지표 및 비고 (소수점 1자리 반영) */
|
||||
{ name: 'STDR_POPLTN_PER_CVPL', fieldName: 'STDR_POPLTN_PER_CVPL', header: { text: '기준인구당\n민원건수\n(건/10,000명)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: 'RMK', header: { text: '비고' } }
|
||||
...reviewAddressColumns,
|
||||
intCol("swtrmtPpltnCnt", "하수처리인구\n(명)"),
|
||||
intCol("whol", "총계"),
|
||||
intCol("dschgWqCnt", "악취"),
|
||||
intCol("odorCnt", "하수관로"),
|
||||
intCol("ldsCnt", "배수설비"),
|
||||
intCol("plltnAcdntCnt", "맨홀"),
|
||||
intCol("swagePplCnt", "정화조"),
|
||||
intCol("sewerCrgAmt", "하수도요금"),
|
||||
intCol("etcCnt", "기타"),
|
||||
avgDecimalCol("crtrPpltnCvlcptEvntcsCnt", "기준인구당\n민원건수\n(건/10,000명)"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN',
|
||||
'POPLTN_SM',
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
"swtrmtPpltnCnt",
|
||||
{
|
||||
name: 'complaintTypeGroup',
|
||||
header: { text: '민원유형(건)' },
|
||||
name: "complaintTypeGroup",
|
||||
header: { text: "민원유형(건)" },
|
||||
items: [
|
||||
'CVPL_CM',
|
||||
'CVPL_DCWTR_QLTWTR',
|
||||
'CVPL_BSML',
|
||||
'CVPL_FIVW',
|
||||
'CVPL_POLLUTN_ACDNT',
|
||||
'CVPL_SSOS',
|
||||
'CVPL_CHRGE',
|
||||
'CVPL_ETC'
|
||||
]
|
||||
"whol",
|
||||
"dschgWqCnt",
|
||||
"odorCnt",
|
||||
"ldsCnt",
|
||||
"plltnAcdntCnt",
|
||||
"swagePplCnt",
|
||||
"sewerCrgAmt",
|
||||
"etcCnt",
|
||||
],
|
||||
},
|
||||
'STDR_POPLTN_PER_CVPL',
|
||||
'RMK'
|
||||
]
|
||||
"crtrPpltnCvlcptEvntcsCnt",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: 'b',
|
||||
tabLabel: '주민친화시설',
|
||||
tabKey: "ST00130002",
|
||||
tabLabel: "주민친화시설",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 5,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
/* 1. 기본 및 시설 정보 */
|
||||
{ name: 'SIDO', fieldName: 'SIDO', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: 'GUGUN', header: { text: '시군구' } },
|
||||
{ name: 'BSC_FCT_NM', fieldName: 'BSC_FCT_NM', header: { text: '처리장명' } },
|
||||
{ name: 'FCT_CPC', fieldName: 'FCT_CPC', header: { text: '시설용량\n(㎥/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'YR_GBN', fieldName: 'YR_GBN', header: { text: '구분' } },
|
||||
|
||||
/* 2. 처리장 전체 면적 (소수점 1자리 반영) */
|
||||
{ name: 'PRCPLT_AR', fieldName: 'PRCPLT_AR', header: { text: '처리장\n면적(㎡)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 3. 주민친화시설 면적 상세 (단위: ㎡ / 정수 반영) */
|
||||
{ name: 'TOTAL_AR', fieldName: 'TOTAL_AR', header: { text: '총계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'RESTRM_AR', fieldName: 'RESTRM_AR', header: { text: '휴게시설' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CHILDPLAY_AR', fieldName: 'CHILDPLAY_AR', header: { text: '어린이놀이공간' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ALSFC_AR', fieldName: 'ALSFC_AR', header: { text: '체력단련시설' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'PLGRND_AR', fieldName: 'PLGRND_AR', header: { text: '운동시설' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'PRKPLCE_AR', fieldName: 'PRKPLCE_AR', header: { text: '주차장' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'CTLSTT_AR', fieldName: 'CTLSTT_AR', header: { text: '교육 및\n문화시설' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'NRPRK_AR', fieldName: 'NRPRK_AR', header: { text: '습지조성\n(생태공원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ETC_AR', fieldName: 'ETC_AR', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
|
||||
/* 4. 비고 */
|
||||
{ name: 'RMK', fieldName: 'RMK', header: { text: '비고' } }
|
||||
...reviewAddressColumns,
|
||||
textCol("swtrmtFcltNm", "처리장명"),
|
||||
intCol("fcltCpct", "시설용량\n(㎥/일)"),
|
||||
decimal1Col("tmplntArea", "처리장\n면적(㎡)"),
|
||||
decimal1Col("whol", "총계"),
|
||||
decimal1Col("brkFcltArea", "휴게시설"),
|
||||
decimal1Col("childFrshpSpacArea", "어린이놀이공간"),
|
||||
decimal1Col("bdySportsFcltArea", "체력단련시설"),
|
||||
decimal1Col("sportsFcltArea", "운동시설"),
|
||||
decimal1Col("pkltArea", "주차장"),
|
||||
decimal1Col("eduMultiFcltArea", "교육 및\n문화시설"),
|
||||
decimal1Col("wtlndMakeArea", "습지조성\n(생태공원)"),
|
||||
decimal1Col("etcCnt", "기타"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'BSC_FCT_NM',
|
||||
'FCT_CPC',
|
||||
'YR_GBN',
|
||||
'PRCPLT_AR',
|
||||
reviewAddressLayout,
|
||||
"displayYear",
|
||||
"swtrmtFcltNm",
|
||||
"fcltCpct",
|
||||
"tmplntArea",
|
||||
{
|
||||
name: 'friendlyFacilityGroup',
|
||||
header: { text: '주민친화시설 면적(㎡)' },
|
||||
name: "friendlyFacilityGroup",
|
||||
header: { text: "주민친화시설 면적(㎡)" },
|
||||
items: [
|
||||
'TOTAL_AR',
|
||||
'RESTRM_AR',
|
||||
'CHILDPLAY_AR',
|
||||
'ALSFC_AR',
|
||||
'PLGRND_AR',
|
||||
'PRKPLCE_AR',
|
||||
'CTLSTT_AR',
|
||||
'NRPRK_AR',
|
||||
'ETC_AR'
|
||||
]
|
||||
"whol",
|
||||
"brkFcltArea",
|
||||
"childFrshpSpacArea",
|
||||
"bdySportsFcltArea",
|
||||
"sportsFcltArea",
|
||||
"pkltArea",
|
||||
"eduMultiFcltArea",
|
||||
"wtlndMakeArea",
|
||||
"etcCnt",
|
||||
],
|
||||
},
|
||||
'RMK'
|
||||
]
|
||||
}
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const SewerageServiceLevelInfoListRvw = rawServiceLevelList.map(tab => ({
|
||||
export const SewerageServiceLevelInfoListRvw = rawServiceLevelList.map((tab) => ({
|
||||
...tab,
|
||||
columns: tab.columns.map(col => ({
|
||||
columns: tab.columns.map((col) => ({
|
||||
...col,
|
||||
editable: false
|
||||
}))
|
||||
}));
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
})),
|
||||
}));
|
||||
|
||||
/** @deprecated `SewerageServiceLevelInfoListRvw` 사용 */
|
||||
export const SewerageServiceLevelInfoList = SewerageServiceLevelInfoListRvw;
|
||||
|
||||
@@ -1,224 +1,366 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// 하수도 통계 관리 10. 물 재이용
|
||||
const rawWaterReuseList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
tabLabel: '공공하수처리수 재이용',
|
||||
rowStartNum: 4,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
/* 1. 기본 및 행정 정보 */
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
import type { GridView } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
footerAvgDecimalArrayByDivision,
|
||||
footerDecimal1ArrayByDivision,
|
||||
footerDecimal2ArrayByDivision,
|
||||
footerIntArrayByDivision,
|
||||
} from "@/components/util/grid/footerSummary";
|
||||
|
||||
/* 2. 하수처리장 기본 정보 */
|
||||
{ name: 'BSC_FCT_NM', fieldName: '시설명', header: { text: '시설명' } },
|
||||
{ name: 'EQP_PRMS_DT', fieldName: '인가일(년/월/일)', header: { text: '인가일\n(년/월/일)' } },
|
||||
{ name: 'FCT_CPC', fieldName: '시설용량(㎥/일)', header: { text: '시설용량\n(㎥/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'TPUT_TOTAL', fieldName: '연간 하수처리량(천톤/년)', header: { text: '연간\n하수처리량\n(천톤/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RSWTR_LAW_YN', fieldName: '법적의무대상(대상/미대상)', header: { text: '법적의무대상\n(대상/미대상)' } },
|
||||
const getDivisionFooterLabel = (grid: GridView, divisionIndex: number, withSuffix: boolean) => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const uniqueValues = new Set<string>();
|
||||
|
||||
/* 3. 재이용시설 현황 */
|
||||
{ name: 'INST_YN', fieldName: '설치유무(유/무)', header: { text: '설치유무\n(유/무)' } },
|
||||
{ name: 'RSWTR_RN_DT', fieldName: '가동개시일 yyyy-mm-dd', header: { text: '가동개시일\nyyyy-mm-dd' } },
|
||||
{ name: 'RSWTR_PRMS_DT', fieldName: '설치승인일 yyyy-mm-dd', header: { text: '설치승인일\nyyyy-mm-dd' } },
|
||||
{ name: 'RSWTR_CPC', fieldName: '용량(톤/일)', header: { text: '용량(톤/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RSWTR_TRT_NM', fieldName: '공법/기자재', header: { text: '공법/기자재' } },
|
||||
{ name: 'RSWTR_PIPE', fieldName: '공급관로길이(km)', header: { text: '공급관로길이\n(km)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RSWTR_PUMP', fieldName: '공급펌프(톤/일)', header: { text: '공급펌프\n(톤/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
for (let i = 0; i < dataSource.getRowCount(); i++) {
|
||||
const val = dataSource.getValue(i, "displayYear");
|
||||
if (val) uniqueValues.add(String(val));
|
||||
}
|
||||
|
||||
/* 4. 재이용 실적 - 총계 및 장내용수 */
|
||||
{ name: 'USE_AMNT_TOTAL', fieldName: '총계', header: { text: '총계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_D01', fieldName: '계(장내)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S01', fieldName: '세척수', header: { text: '세척수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S02', fieldName: '냉각수', header: { text: '냉각수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S03', fieldName: '청소수', header: { text: '청소수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S04', fieldName: '조경용수(장내)', header: { text: '조경용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S05', fieldName: '희석용수', header: { text: '희석용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S06', fieldName: '장내중수도', header: { text: '장내중수도' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S07', fieldName: '장내기타용수', header: { text: '장내기타용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
const divisionValues = Array.from(uniqueValues);
|
||||
if (divisionValues.length <= divisionIndex) {
|
||||
return withSuffix ? "합계" : "";
|
||||
}
|
||||
|
||||
/* 5. 재이용 실적 - 장외용수 */
|
||||
{ name: 'USE_AMNT_D02', fieldName: '계(장외)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S11', fieldName: '청소·화장실 용수', header: { text: '청소·화장실\n용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S12', fieldName: '세척·살수용수', header: { text: '세척·살수용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S13', fieldName: '조경용수(장외)', header: { text: '조경용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S14', fieldName: '친수용수', header: { text: '친수용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S15', fieldName: '하천등 유지용수', header: { text: '하천등 유지용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S16', fieldName: '농업용수', header: { text: '농업용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S17', fieldName: '지하수충전', header: { text: '지하수충전' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'USE_AMNT_S18', fieldName: '공업용수', header: { text: '공업용수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
const label = divisionValues[divisionIndex] ?? "";
|
||||
return withSuffix ? `${label}합계` : label;
|
||||
};
|
||||
|
||||
/* 6. 지표 및 비고 */
|
||||
{ name: 'REUSE_RT', fieldName: '재이용률(%)', header: { text: '재이용률\n(%)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN',
|
||||
{
|
||||
name: 'sewagePlantGroup',
|
||||
header: { text: '하수처리장' },
|
||||
items: ['BSC_FCT_NM', 'EQP_PRMS_DT', 'FCT_CPC', 'TPUT_TOTAL', 'RSWTR_LAW_YN']
|
||||
},
|
||||
{
|
||||
name: 'reuseFctGroup',
|
||||
header: { text: '재이용시설' },
|
||||
items: [
|
||||
'INST_YN',
|
||||
{
|
||||
name: 'installDetailGroup',
|
||||
header: { text: '재이용시설 설치시' },
|
||||
items: [
|
||||
'RSWTR_RN_DT',
|
||||
'RSWTR_PRMS_DT',
|
||||
{
|
||||
name: 'reProcessGroup',
|
||||
header: { text: '재처리시설' },
|
||||
items: ['RSWTR_CPC', 'RSWTR_TRT_NM']
|
||||
},
|
||||
'RSWTR_PIPE',
|
||||
'RSWTR_PUMP'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'reuseStatusGroup',
|
||||
header: { text: '하수처리수 재이용현황(천톤/년)' },
|
||||
items: [
|
||||
'USE_AMNT_TOTAL',
|
||||
{
|
||||
name: 'internalUseGroup',
|
||||
header: { text: '장 내 용 수' },
|
||||
items: [
|
||||
'USE_AMNT_D01', 'USE_AMNT_S01', 'USE_AMNT_S02', 'USE_AMNT_S03',
|
||||
'USE_AMNT_S04', 'USE_AMNT_S05', 'USE_AMNT_S06', 'USE_AMNT_S07'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'externalUseGroup',
|
||||
header: { text: '장 외 용 수' },
|
||||
items: [
|
||||
'USE_AMNT_D02', 'USE_AMNT_S11', 'USE_AMNT_S12', 'USE_AMNT_S13',
|
||||
'USE_AMNT_S14', 'USE_AMNT_S15', 'USE_AMNT_S16', 'USE_AMNT_S17', 'USE_AMNT_S18'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
'REUSE_RT',
|
||||
'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'b',
|
||||
tabLabel: '중수도 현황',
|
||||
rowStartNum: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'BILD_NM', fieldName: '건축물명/개발사업명', header: { text: '건축물명/\n개발사업명' } },
|
||||
{ name: 'ADDR', fieldName: '주소', header: { text: '주소' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'BILD_TP', fieldName: '시설물용도/개발사업종류', header: { text: '시설물용도/\n개발사업종류' } },
|
||||
{ name: 'LAW_YN', fieldName: '법적의무 대상여부(대상/미대상)', header: { text: '법적의무\n대상여부\n(대상/미대상)' } },
|
||||
{ name: 'COMP_DT', fieldName: '설치완료일 yyyy-mm-dd', header: { text: '설치완료일\nyyyy-mm-dd' } },
|
||||
{ name: 'RN_DT', fieldName: '가동개시일 yyyy-mm-dd', header: { text: '가동개시일\nyyyy-mm-dd' } },
|
||||
{ name: 'TOTAR', fieldName: '건축연면적(㎡)', header: { text: '건축연면적\n(㎡)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'PROC_CPCTY', fieldName: '처리용량(㎥/일)', header: { text: '처리용량\n(㎥/일)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'WATER_USE_CPCTY', fieldName: '물 사용량(㎥/년)', header: { text: '물 사용량\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RUSWTR_USE_SM', fieldName: '중수도 이용량(㎥/년)', header: { text: '중수도\n이용량\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'SUB_USE_CPCTY', fieldName: '대체수 이용량(㎥/년)', header: { text: '대체수\n이용량\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RUSWTR_PRPOS', fieldName: '중수도용도', header: { text: '중수도용도' } },
|
||||
{ name: 'FYER_OPER_CT', fieldName: '연간운영비(백만원/년)', header: { text: '연간운영비\n(백만원/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } }
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'BILD_NM', 'ADDR', 'YR_GBN', 'BILD_TP', 'LAW_YN', 'COMP_DT', 'RN_DT',
|
||||
{
|
||||
name: 'fctStatusGroup',
|
||||
header: { text: '시설현황' },
|
||||
items: ['TOTAR', 'PROC_CPCTY']
|
||||
},
|
||||
'WATER_USE_CPCTY', 'RUSWTR_USE_SM', 'SUB_USE_CPCTY', 'RUSWTR_PRPOS', 'FYER_OPER_CT', 'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'c',
|
||||
tabLabel: '빗물이용시설 현황',
|
||||
rowStartNum: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'RWR_REUSE_FCT_NM', fieldName: '시설명', header: { text: '시설명' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'BILD_TP', fieldName: '건축물용도', header: { text: '건축물용도' } },
|
||||
{ name: 'ADDR', fieldName: '위치(주소)', header: { text: '위치\n(주소)' } },
|
||||
{ name: 'LAW_YN', fieldName: '법적의무 대상여부(대상/미대상)', header: { text: '법적의무\n대상여부\n(대상/미대상)' } },
|
||||
{ name: 'RN_DT', fieldName: '설치완료일 yyyy-mm-dd', header: { text: '설치완료일\nyyyy-mm-dd' } },
|
||||
{ name: 'INSTL_CT', fieldName: '설치비(백만원)', header: { text: '설치비\n(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'CCM', fieldName: '집수면 종류', header: { text: '집수면 종류' } },
|
||||
{ name: 'TRTFC_ENNC', fieldName: '여과등 처리시설 유무', header: { text: '여과등\n처리시설\n유무' } },
|
||||
{ name: 'SGBS_CPCTY', fieldName: '저류조 용량(㎥)', header: { text: '저류조 용량\n(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'FYER_USEQTY', fieldName: '연간 이용량(㎥/년)', header: { text: '연간 이용량\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RWR_PRCUSE_PRPPOS', fieldName: '이용용도', header: { text: '이용용도' } },
|
||||
{ name: 'FYER_OPER_CT', fieldName: '연간운영비(백만원/년)', header: { text: '연간운영비\n(백만원/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'RWR_REUSE_FCT_NM', 'YR_GBN', 'BILD_TP', 'ADDR', 'LAW_YN', 'RN_DT', 'INSTL_CT', 'CCM', 'TRTFC_ENNC', 'SGBS_CPCTY', 'FYER_USEQTY', 'RWR_PRCUSE_PRPPOS', 'FYER_OPER_CT', 'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'd',
|
||||
tabLabel: '하ㆍ폐수처리수 재이용시설 시공업 등록현황',
|
||||
rowStartNum: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'CMPNM', fieldName: '상호(명칭)', header: { text: '상호(명칭)' } },
|
||||
{ name: 'RPRSNTV', fieldName: '대표자', header: { text: '대표자' } },
|
||||
{ name: 'ADDR', fieldName: '주소', header: { text: '주소' } },
|
||||
{ name: 'CMPN_RGSDE', fieldName: '등록일 yyyy-mm-dd', header: { text: '등록일\nyyyy-mm-dd' } },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
{ name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' } },
|
||||
{ name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN', 'CMPNM', 'RPRSNTV', 'ADDR', 'CMPN_RGSDE', 'RMK'
|
||||
]
|
||||
},
|
||||
{
|
||||
tabKey: 'e',
|
||||
tabLabel: '하ㆍ폐수처리수 재이용시설 시공업 인가현황',
|
||||
rowStartNum: 2,
|
||||
columns: [
|
||||
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'CMPNM', fieldName: '상호(명칭)', header: { text: '상호(명칭)' } },
|
||||
{ name: 'RPRSNTV', fieldName: '대표자', header: { text: '대표자' } },
|
||||
{ name: 'ADDR', fieldName: '주소', header: { text: '주소' } },
|
||||
{ name: 'CMPN_RGSDE', fieldName: '인가일(년/월/일)', header: { text: '인가일\n(년/월/일)' } },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } }
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] },
|
||||
'YR_GBN', 'CMPNM', 'RPRSNTV', 'ADDR', 'CMPN_RGSDE', 'RMK'
|
||||
]
|
||||
}
|
||||
const divisionFooterCallbacks = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) },
|
||||
];
|
||||
|
||||
export const WaterReuseInfoList = rawWaterReuseList.map(tab => ({
|
||||
...tab,
|
||||
columns: tab.columns.map(col => ({
|
||||
...col,
|
||||
editable: false
|
||||
}))
|
||||
}));
|
||||
const divisionFooterLabelOnly = [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) },
|
||||
];
|
||||
|
||||
const reviewFacilityAddressColumns = [
|
||||
{
|
||||
name: "ctpvNm",
|
||||
fieldName: "ctpvNm",
|
||||
header: { text: "시도" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
footer: divisionFooterCallbacks,
|
||||
},
|
||||
{
|
||||
name: "sggNm",
|
||||
fieldName: "sggNm",
|
||||
header: { text: "시군구" },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
},
|
||||
{
|
||||
name: "displayYear",
|
||||
fieldName: "displayYear",
|
||||
header: { text: "구분" },
|
||||
sortdisable: true,
|
||||
footer: divisionFooterLabelOnly,
|
||||
},
|
||||
];
|
||||
|
||||
const reviewFacilityAddressLayout = {
|
||||
name: "areaGroup",
|
||||
header: { text: "행정구역" },
|
||||
items: [
|
||||
{ column: "ctpvNm", footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] },
|
||||
"sggNm",
|
||||
],
|
||||
};
|
||||
|
||||
const textCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
});
|
||||
|
||||
const decimal1Col = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerDecimal1ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
});
|
||||
|
||||
const decimal2Col = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerDecimal2ArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.00",
|
||||
});
|
||||
|
||||
const intCol = (name: string, header: string) => ({
|
||||
name,
|
||||
fieldName: name,
|
||||
header: { text: header },
|
||||
footer: footerIntArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0",
|
||||
});
|
||||
|
||||
// 하수도 통계 검토 10. 물 재이용
|
||||
const rawWaterReuseList = [
|
||||
{
|
||||
tabKey: "ST00100001",
|
||||
tabLabel: "공공하수처리수 재이용",
|
||||
rowStartNum: 4,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewFacilityAddressColumns,
|
||||
textCol("swtrmtFcltNm", "시설명"),
|
||||
textCol("sncnYmd", "인가일\n(년/월/일)"),
|
||||
decimal1Col("prcsFcltCpct", "시설용량\n(㎥/일)"),
|
||||
decimal1Col("prcsQnt", "연간\n하수처리량\n(천톤/년)"),
|
||||
textCol("lglYn", "법적의무대상\n(대상/미대상)"),
|
||||
textCol("instlYn", "설치유무\n(유/무)"),
|
||||
textCol("oprtngStrtYmd", "가동개시일\nyyyy-mm-dd"),
|
||||
textCol("instlAprvYmd", "설치승인일\nyyyy-mm-dd"),
|
||||
decimal1Col("rprcsFcltCpct", "용량(톤/일)"),
|
||||
textCol("mtcnsNm", "공법/기자재"),
|
||||
decimal2Col("ruswtPplLen", "공급관로길이\n(km)"),
|
||||
decimal1Col("ruswtPumpLen", "공급펌프\n(톤/일)"),
|
||||
decimal1Col("wholUseQnt", "총계"),
|
||||
decimal1Col("ithlWshwtrQnt", "계"),
|
||||
decimal1Col("ithlClnQty", "세척수"),
|
||||
decimal1Col("ithlFrzgQty", "냉각수"),
|
||||
decimal1Col("ithlLdhcUswtrQnt", "청소수"),
|
||||
decimal1Col("ithlDltnUswtrQnt", "조경용수"),
|
||||
decimal1Col("ithlRuswtrQnt", "희석용수"),
|
||||
decimal1Col("ithlEtcQnt", "장내중수도"),
|
||||
decimal1Col("ithlUnsdQnt", "장내기타용수"),
|
||||
decimal1Col("otcntrUswtrWhol", "계"),
|
||||
decimal1Col("otcntrClnRstrmQnt", "청소·화장실\n용수"),
|
||||
decimal1Col("otcntrWshwtrQnt", "세척·살수용수"),
|
||||
decimal1Col("otcntrLdhcUswtrQnt", "조경용수"),
|
||||
decimal1Col("otcntrHdrplUswtrQnt", "친수용수"),
|
||||
decimal1Col("otcntrRvrKeepQnt", "하천등 유지용수"),
|
||||
decimal1Col("agrcltUswtrPstnQnt", "농업용수"),
|
||||
decimal1Col("ugwtrChargQnt", "지하수충전"),
|
||||
decimal1Col("mdsrUswtrQnt", "공업용수"),
|
||||
{
|
||||
name: "rtlztnRt",
|
||||
fieldName: "rtlztnRt",
|
||||
header: { text: "재이용률\n(%)" },
|
||||
footer: footerAvgDecimalArrayByDivision,
|
||||
valueType: ValueType.NUMBER,
|
||||
numberFormat: "#,##0.0",
|
||||
},
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
reviewFacilityAddressLayout,
|
||||
"displayYear",
|
||||
{
|
||||
name: "seweragePlantGroup",
|
||||
header: { text: "하수처리장" },
|
||||
items: ["swtrmtFcltNm", "sncnYmd", "prcsFcltCpct", "prcsQnt", "lglYn"],
|
||||
},
|
||||
{
|
||||
name: "reuseFacilityGroup",
|
||||
header: { text: "재이용시설" },
|
||||
items: [
|
||||
"instlYn",
|
||||
{
|
||||
name: "installationGroup",
|
||||
header: { text: "재이용시설 설치시" },
|
||||
items: [
|
||||
"oprtngStrtYmd",
|
||||
"instlAprvYmd",
|
||||
{
|
||||
name: "reProcessingFacilityGroup",
|
||||
header: { text: "재처리시설" },
|
||||
items: ["rprcsFcltCpct", "mtcnsNm"],
|
||||
},
|
||||
"ruswtPplLen",
|
||||
"ruswtPumpLen",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "reuseStatusGroup",
|
||||
header: { text: "하수처리수 재이용현황(천톤/년)" },
|
||||
items: [
|
||||
"wholUseQnt",
|
||||
{
|
||||
name: "internalWaterGroup",
|
||||
header: { text: "장 내 용 수" },
|
||||
items: [
|
||||
"ithlWshwtrQnt",
|
||||
"ithlClnQty",
|
||||
"ithlFrzgQty",
|
||||
"ithlLdhcUswtrQnt",
|
||||
"ithlDltnUswtrQnt",
|
||||
"ithlRuswtrQnt",
|
||||
"ithlEtcQnt",
|
||||
"ithlUnsdQnt",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "externalWaterGroup",
|
||||
header: { text: "장 외 용 수" },
|
||||
items: [
|
||||
"otcntrUswtrWhol",
|
||||
"otcntrClnRstrmQnt",
|
||||
"otcntrWshwtrQnt",
|
||||
"otcntrLdhcUswtrQnt",
|
||||
"otcntrHdrplUswtrQnt",
|
||||
"otcntrRvrKeepQnt",
|
||||
"agrcltUswtrPstnQnt",
|
||||
"ugwtrChargQnt",
|
||||
"mdsrUswtrQnt",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"rtlztnRt",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00100002",
|
||||
tabLabel: "중수도 현황",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewFacilityAddressColumns,
|
||||
textCol("fcltNm", "건축물명/\n개발사업명"),
|
||||
textCol("bldgSeCn", "시설물용도/\n개발사업종류"),
|
||||
textCol("fcltPstnCn", "주소"),
|
||||
textCol("lglTrgtYn", "법적의무\n대상여부\n(대상/미대상)"),
|
||||
textCol("cmptnStrtYmd", "설치완료일\nyyyy-mm-dd"),
|
||||
textCol("oprtngStrtYmd", "가동개시일\nyyyy-mm-dd"),
|
||||
intCol("archWholArea", "건축연면적\n(㎡)"),
|
||||
decimal1Col("prcsCpct", "처리용량\n(㎥/일)"),
|
||||
intCol("wtrUseQnt", "물 사용량\n(㎥/년)"),
|
||||
intCol("ruswtrUtztnQnt", "중수도\n이용량\n(㎥/년)"),
|
||||
intCol("sbstCntUtztnQnt", "대체수\n이용량\n(㎥/년)"),
|
||||
textCol("mnUsgCn", "중수도용도"),
|
||||
decimal1Col("ruswtrOperAmt", "연간운영비\n(백만원/년)"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
reviewFacilityAddressLayout,
|
||||
"displayYear",
|
||||
"fcltNm",
|
||||
"bldgSeCn",
|
||||
"fcltPstnCn",
|
||||
"lglTrgtYn",
|
||||
"cmptnStrtYmd",
|
||||
"oprtngStrtYmd",
|
||||
{
|
||||
name: "facilityStatusGroup",
|
||||
header: { text: "시설현황" },
|
||||
items: ["archWholArea", "prcsCpct"],
|
||||
},
|
||||
"wtrUseQnt",
|
||||
"ruswtrUtztnQnt",
|
||||
"sbstCntUtztnQnt",
|
||||
"mnUsgCn",
|
||||
"ruswtrOperAmt",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00100003",
|
||||
tabLabel: "빗물이용시설 현황",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewFacilityAddressColumns,
|
||||
textCol("fcltNm", "시설명"),
|
||||
textCol("bdstTypCn", "건축물용도"),
|
||||
textCol("fcltPstnCn", "위치\n(주소)"),
|
||||
textCol("lglTrgtYn", "법적의무\n대상여부\n(대상/미대상)"),
|
||||
textCol("instlCmptnYmd", "설치완료일\nyyyy-mm-dd"),
|
||||
decimal1Col("instlAmt", "설치비\n(백만원)"),
|
||||
textCol("rcptMynCn", "집수면 종류"),
|
||||
textCol("filtrPrcsFcltYnExpln", "여과등\n처리시설\n유무"),
|
||||
intCol("impdCpct", "저류조 용량\n(㎥)"),
|
||||
intCol("anlUseQnt", "연간 이용량\n(㎥/년)"),
|
||||
textCol("exlnUtlzUsgExpln", "이용용도"),
|
||||
decimal1Col("anlOperAmt", "연간운영비\n(백만원/년)"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
reviewFacilityAddressLayout,
|
||||
"displayYear",
|
||||
"fcltNm",
|
||||
"bdstTypCn",
|
||||
"fcltPstnCn",
|
||||
"lglTrgtYn",
|
||||
"instlCmptnYmd",
|
||||
"instlAmt",
|
||||
"rcptMynCn",
|
||||
"filtrPrcsFcltYnExpln",
|
||||
"impdCpct",
|
||||
"anlUseQnt",
|
||||
"exlnUtlzUsgExpln",
|
||||
"anlOperAmt",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00100004",
|
||||
tabLabel: "하ㆍ폐수처리수 재이용시설 시공업 등록현황",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewFacilityAddressColumns,
|
||||
textCol("bplcNm", "상호(명칭)"),
|
||||
textCol("rprsvNm", "대표자"),
|
||||
textCol("bplcPstnCn", "주소"),
|
||||
textCol("regDt", "등록일\nyyyy-mm-dd"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
reviewFacilityAddressLayout,
|
||||
"displayYear",
|
||||
"bplcNm",
|
||||
"rprsvNm",
|
||||
"bplcPstnCn",
|
||||
"regDt",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
{
|
||||
tabKey: "ST00100005",
|
||||
tabLabel: "하ㆍ폐수처리수 재이용시설 시공업 인가현황",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
...reviewFacilityAddressColumns,
|
||||
textCol("bplcNm", "상호(명칭)"),
|
||||
textCol("rprsvNm", "대표자"),
|
||||
textCol("bplcPstnCn", "주소"),
|
||||
textCol("coRegYmd", "인가일\n(년/월/일)"),
|
||||
textCol("rmrk", "비고"),
|
||||
],
|
||||
columnLayout: [
|
||||
reviewFacilityAddressLayout,
|
||||
"displayYear",
|
||||
"bplcNm",
|
||||
"rprsvNm",
|
||||
"bplcPstnCn",
|
||||
"coRegYmd",
|
||||
"rmrk",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const WaterReuseInfoListRvw = rawWaterReuseList.map((tab) => ({
|
||||
...tab,
|
||||
columns: tab.columns.map((col) => ({
|
||||
...col,
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
})),
|
||||
}));
|
||||
|
||||
/** @deprecated `WaterReuseInfoListRvw` 사용 */
|
||||
export const WaterReuseInfoList = WaterReuseInfoListRvw;
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
import type { CellStyleCallback } from "@/components/type/DataGrid.type";
|
||||
|
||||
/** 검토(review) 그리드: rowType이 CURR인 행(당해 연도) 흰색 배경 */
|
||||
export const reviewRowTypeCurrCellStyleCallback: CellStyleCallback = ({ row }) => {
|
||||
if (String(row.rowType ?? "") === "CURR") {
|
||||
return "rg-cell-rowtype-curr";
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 객체 배열을 그리드 row 형식으로 변환 (null/undefined → "").
|
||||
* 어떤 엔티티 타입이든 공통 사용 가능.
|
||||
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
type TreatWaterReuseDesignConstStatusSaveResponse,
|
||||
type TreatWaterReuseDesignConstStatusSubmitRequest,
|
||||
type TreatWaterReuseDesignConstStatusSubmitResponse,
|
||||
type WaterReuseOperationDataUpdateRequest,
|
||||
type WaterReuseOperationDataUpdateResponse,
|
||||
type WaterReuseRequestParams,
|
||||
} from "../../model/input/statistics-input-water-reuse-interface";
|
||||
|
||||
@@ -199,6 +201,17 @@ export class StatisticsInputWaterReuseApiService implements StatisticsInputWater
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 공공하수처리수 재이용 운영 데이터 업데이트 — ST00100001 */
|
||||
public async updateWaterReuseOperationData(
|
||||
request: WaterReuseOperationDataUpdateRequest,
|
||||
): Promise<WaterReuseOperationDataUpdateResponse> {
|
||||
const response = await stClient.put<WaterReuseOperationDataUpdateResponse>(
|
||||
`${BASE}/operation-data/update`,
|
||||
request,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const statisticsInputWaterReuseApi = new StatisticsInputWaterReuseApiService();
|
||||
|
||||
+12
@@ -9,6 +9,8 @@ import type {
|
||||
PublicSewageFacilitySaveResponse,
|
||||
PublicSewageFacilitySubmitRequest,
|
||||
PublicSewageFacilitySubmitResponse,
|
||||
PublicSewageFacilityOperationDataUpdateRequest,
|
||||
PublicSewageFacilityOperationDataUpdateResponse,
|
||||
PublicSewageMainFacilityMappingSaveRequest,
|
||||
PublicSewageMainFacilityMappingSaveResponse,
|
||||
PublicSewageMainFacilityResponse,
|
||||
@@ -73,6 +75,16 @@ export class StatisticsInputPublicSewageFacilityApiService implements Statistics
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updatePublicSewageFacilityOperationData(
|
||||
param: PublicSewageFacilityOperationDataUpdateRequest,
|
||||
): Promise<PublicSewageFacilityOperationDataUpdateResponse> {
|
||||
const response = await stClient.put<PublicSewageFacilityOperationDataUpdateResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/operation-data/update`,
|
||||
param,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const statisticsInputPublicSewageFacilityApiService = new StatisticsInputPublicSewageFacilityApiService();
|
||||
|
||||
@@ -35,6 +35,34 @@ import {
|
||||
type PublicSewageFacilityReviewApiResponse,
|
||||
type PublicSewageFacilityReviewRequestParams,
|
||||
} from "../model/statistics-public-sewage-facility-review-interface";
|
||||
import {
|
||||
type ExcretaTreatmentFacilityReviewApiResponse,
|
||||
type ExcretaTreatmentFacilityReviewRequestParams,
|
||||
} from "../model/statistics-excreta-treatment-facility-review-interface";
|
||||
import {
|
||||
type PrivateSewageFacilityReviewApiResponse,
|
||||
type PrivateSewageFacilityReviewRequestParams,
|
||||
} from "../model/statistics-private-sewage-facility-review-interface";
|
||||
import {
|
||||
type SewerageFinanceReviewApiResponse,
|
||||
type SewerageFinanceReviewRequestParams,
|
||||
} from "../model/statistics-sewerage-finance-review-interface";
|
||||
import {
|
||||
type WaterReuseReviewApiResponse,
|
||||
type WaterReuseReviewRequestParams,
|
||||
} from "../model/statistics-water-reuse-review-interface";
|
||||
import {
|
||||
type SewageExcretaSlurgeReviewApiResponse,
|
||||
type SewageExcretaSlurgeReviewRequestParams,
|
||||
} from "../model/statistics-sewage-excreta-slurge-review-interface";
|
||||
import {
|
||||
type SewageExcretaRelatedReviewApiResponse,
|
||||
type SewageExcretaRelatedReviewRequestParams,
|
||||
} from "../model/statistics-sewage-excreta-related-review-interface";
|
||||
import {
|
||||
type SewerageServiceLevelReviewApiResponse,
|
||||
type SewerageServiceLevelReviewRequestParams,
|
||||
} from "../model/statistics-sewerage-service-level-review-interface";
|
||||
|
||||
|
||||
export class StatisticsManagementApiService {
|
||||
@@ -80,6 +108,20 @@ export class StatisticsManagementApiService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 07. 분뇨처리시설 검토 목록 조회
|
||||
* @param params (sido, gugun, yr, bsnmCd, fctName)
|
||||
*/
|
||||
public async getExcretaTreatmentFacilityReviewList(
|
||||
params: ExcretaTreatmentFacilityReviewRequestParams,
|
||||
): Promise<ExcretaTreatmentFacilityReviewApiResponse> {
|
||||
const response = await stClient.get<ExcretaTreatmentFacilityReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/excreta-treatment`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 02. 하수관로 지역별 시설현황 목록 조회 (Tab1)
|
||||
* @param params (sido, gugun, yr, bsnmCd)
|
||||
@@ -226,6 +268,292 @@ export class StatisticsManagementApiService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 08. 개인하수처리시설 tab1. 처리용량별 오수처리시설 현황 검토 조회 — ST00080001
|
||||
*/
|
||||
public async getPrivateSewageTreatFactStatusReviewList(
|
||||
params: PrivateSewageFacilityReviewRequestParams,
|
||||
): Promise<PrivateSewageFacilityReviewApiResponse> {
|
||||
const response = await stClient.get<PrivateSewageFacilityReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/private-sevage`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 08. 개인하수처리시설 tab2. 정화조 현황 검토 조회 — ST00080002
|
||||
*/
|
||||
public async getSepticTankStatusReviewList(
|
||||
params: PrivateSewageFacilityReviewRequestParams,
|
||||
): Promise<PrivateSewageFacilityReviewApiResponse> {
|
||||
const response = await stClient.get<PrivateSewageFacilityReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/private-tank-status`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 08. 개인하수처리시설 tab3. 개인하수도 관리지역 지정공고 검토 조회 — ST00080003
|
||||
*/
|
||||
public async getPrivateSewageDesigStatusReviewList(
|
||||
params: PrivateSewageFacilityReviewRequestParams,
|
||||
): Promise<PrivateSewageFacilityReviewApiResponse> {
|
||||
const response = await stClient.get<PrivateSewageFacilityReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/private-mgmt-area`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 09. 하수도 재정 tab1. 하수도재정 결산 검토 조회 — ST00090001
|
||||
*/
|
||||
public async getSewerageFinanceSettleReviewList(
|
||||
params: SewerageFinanceReviewRequestParams,
|
||||
): Promise<SewerageFinanceReviewApiResponse> {
|
||||
const response = await stClient.get<SewerageFinanceReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/sewerage-financial`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 09. 하수도 재정 tab2. 하수도 요금 검토 조회 — ST00090002
|
||||
*/
|
||||
public async getSewerageChargeReviewList(
|
||||
params: SewerageFinanceReviewRequestParams,
|
||||
): Promise<SewerageFinanceReviewApiResponse> {
|
||||
const response = await stClient.get<SewerageFinanceReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/sewerage-fee`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 09. 하수도 재정 tab3. 하수도 사용료 검토 조회 — ST00090003
|
||||
*/
|
||||
public async getSewerageUsageFeeReviewList(
|
||||
params: SewerageFinanceReviewRequestParams,
|
||||
): Promise<SewerageFinanceReviewApiResponse> {
|
||||
const response = await stClient.get<SewerageFinanceReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/sewerage-usage`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 09. 하수도 재정 tab4. 원인자부담금 검토 조회 — ST00090004
|
||||
*/
|
||||
public async getCauserBurdenReviewList(
|
||||
params: SewerageFinanceReviewRequestParams,
|
||||
): Promise<SewerageFinanceReviewApiResponse> {
|
||||
const response = await stClient.get<SewerageFinanceReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/sewerage-org-cause`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 10. 물 재이용 tab1. 공공하수처리수 재이용 검토 조회 — ST00100001
|
||||
*/
|
||||
public async getWaterReuseReuseStatusReviewList(
|
||||
params: WaterReuseReviewRequestParams,
|
||||
): Promise<WaterReuseReviewApiResponse> {
|
||||
const response = await stClient.get<WaterReuseReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/pbl-swage-ruswt`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 10. 물 재이용 tab2. 중수도 현황 검토 조회 — ST00100002
|
||||
*/
|
||||
public async getWaterReuseGrayWaterReviewList(
|
||||
params: WaterReuseReviewRequestParams,
|
||||
): Promise<WaterReuseReviewApiResponse> {
|
||||
const response = await stClient.get<WaterReuseReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/gray-water-status`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 10. 물 재이용 tab3. 빗물이용시설 현황 검토 조회 — ST00100003
|
||||
*/
|
||||
public async getWaterReuseRainwaterReviewList(
|
||||
params: WaterReuseReviewRequestParams,
|
||||
): Promise<WaterReuseReviewApiResponse> {
|
||||
const response = await stClient.get<WaterReuseReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/water-reuse`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 10. 물 재이용 tab4. 하·폐수처리수 재이용시설 시공업 등록현황 검토 조회 — ST00100004
|
||||
*/
|
||||
public async getWaterReuseRegStatusReviewList(
|
||||
params: WaterReuseReviewRequestParams,
|
||||
): Promise<WaterReuseReviewApiResponse> {
|
||||
const response = await stClient.get<WaterReuseReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/wwt-rtlztn-reg`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 10. 물 재이용 tab5. 하·폐수처리수 재이용시설 시공업 인가현황 검토 조회 — ST00100005
|
||||
*/
|
||||
public async getWaterReuseSncnStatusReviewList(
|
||||
params: WaterReuseReviewRequestParams,
|
||||
): Promise<WaterReuseReviewApiResponse> {
|
||||
const response = await stClient.get<WaterReuseReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/wwt-rtlztn-aprv`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 11. 하수 및 분뇨 찌꺼기 tab1. 찌꺼기(슬러지)처리시설현황 검토 조회 — ST00110001
|
||||
*/
|
||||
public async getSludgeProcessingReviewList(
|
||||
params: SewageExcretaSlurgeReviewRequestParams,
|
||||
): Promise<SewageExcretaSlurgeReviewApiResponse> {
|
||||
const response = await stClient.get<SewageExcretaSlurgeReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/sludge-processing`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 11. 하수 및 분뇨 찌꺼기 tab2. 소화조 운영현황 검토 조회 — ST00110002
|
||||
*/
|
||||
public async getSludgeDigestionTankReviewList(
|
||||
params: SewageExcretaSlurgeReviewRequestParams,
|
||||
): Promise<SewageExcretaSlurgeReviewApiResponse> {
|
||||
const response = await stClient.get<SewageExcretaSlurgeReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/sludge-digestion-tank`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 11. 하수 및 분뇨 찌꺼기 tab3. 하수찌꺼기 처리시설 운영현황 검토 조회 — ST00110003
|
||||
*/
|
||||
public async getSludgeFacilityOperReviewList(
|
||||
params: SewageExcretaSlurgeReviewRequestParams,
|
||||
): Promise<SewageExcretaSlurgeReviewApiResponse> {
|
||||
const response = await stClient.get<SewageExcretaSlurgeReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/sludge-facility-oper`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 11. 하수 및 분뇨 찌꺼기 tab4. 분뇨찌꺼기(슬러지)처리현황 검토 조회 — ST00110004
|
||||
*/
|
||||
public async getHumanWasteSludgeReviewList(
|
||||
params: SewageExcretaSlurgeReviewRequestParams,
|
||||
): Promise<SewageExcretaSlurgeReviewApiResponse> {
|
||||
const response = await stClient.get<SewageExcretaSlurgeReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/sludge-human-waste`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12. 하수 및 분뇨 관련 산업 tab1. 공공기관 하수분야 검토 조회 — ST00120001
|
||||
*/
|
||||
public async getPublicSewageStaffReviewList(
|
||||
params: SewageExcretaRelatedReviewRequestParams,
|
||||
): Promise<SewageExcretaRelatedReviewApiResponse> {
|
||||
const response = await stClient.get<SewageExcretaRelatedReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/public-sewage-staff`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12. 하수 및 분뇨 관련 산업 tab2. 공공기관 분뇨분야 검토 조회 — ST00120002
|
||||
*/
|
||||
public async getPublicHumanWasteStaffReviewList(
|
||||
params: SewageExcretaRelatedReviewRequestParams,
|
||||
): Promise<SewageExcretaRelatedReviewApiResponse> {
|
||||
const response = await stClient.get<SewageExcretaRelatedReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/public-human-waste-staff`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12. 하수 및 분뇨 관련 산업 tab3. 분뇨수집·운반업 검토 조회 — ST00120003
|
||||
*/
|
||||
public async getHumanWasteCollectionReviewList(
|
||||
params: SewageExcretaRelatedReviewRequestParams,
|
||||
): Promise<SewageExcretaRelatedReviewApiResponse> {
|
||||
const response = await stClient.get<SewageExcretaRelatedReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/human-waste-collection`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12. 하수 및 분뇨 관련 산업 tab4. 개인하수처리관련업 검토 조회 — ST00120004
|
||||
*/
|
||||
public async getIndvSwtrmtRelBsnsReviewList(
|
||||
params: SewageExcretaRelatedReviewRequestParams,
|
||||
): Promise<SewageExcretaRelatedReviewApiResponse> {
|
||||
const response = await stClient.get<SewageExcretaRelatedReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/indv-swtrmt-rel-bsns`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 13. 하수도 서비스 수준 tab1. 민원 검토 조회 — ST00130001
|
||||
*/
|
||||
public async getSewageComplaintReviewList(
|
||||
params: SewerageServiceLevelReviewRequestParams,
|
||||
): Promise<SewerageServiceLevelReviewApiResponse> {
|
||||
const response = await stClient.get<SewerageServiceLevelReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/sewage-complaint`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 13. 하수도 서비스 수준 tab2. 주민친화시설 검토 조회 — ST00130002
|
||||
*/
|
||||
public async getResidentFriendlyFacilityReviewList(
|
||||
params: SewerageServiceLevelReviewRequestParams,
|
||||
): Promise<SewerageServiceLevelReviewApiResponse> {
|
||||
const response = await stClient.get<SewerageServiceLevelReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/resident-friendly-facility`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getReviewHistory(params: ReviewHistoryRequest): Promise<ReviewHistoryResponse> {
|
||||
const response = await stClient.get<ReviewHistoryResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/history`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { client, stClient } from "@/shared/api/client";
|
||||
import { withAuthHeader } from "@/shared/api/with-auth-header";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import {
|
||||
@@ -310,7 +311,7 @@ export class StatisticsApiService implements StatisticsService {
|
||||
public async getReviewerInfo(param: ReviewerInfoRequest): Promise<ReviewerInfoResponse> {
|
||||
const response = await stClient.get<ReviewerInfoResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/reviewer-info`,
|
||||
{ params: param },
|
||||
withAuthHeader({ params: param }),
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
+6
-6
@@ -46,15 +46,15 @@ export interface SewageReservoirItem {
|
||||
wtrStrgZoneArea: number;
|
||||
impdFcltCpct: number;
|
||||
grssPpqty: number;
|
||||
pumpFcltN1DmtrExpln: string;
|
||||
pumpFcltN1DmtrExpln: number;
|
||||
pumpFcltN1PerPpqty: number;
|
||||
pumpFcltN1PumpCnt: number;
|
||||
pumpFcltN1RsrvPumpCnt: number;
|
||||
pumpFcltN2DmtrExpln: string;
|
||||
pumpFcltN2DmtrExpln: number;
|
||||
pumpFcltN2PerPpqty: number;
|
||||
pumpFcltN2PumpCnt: number;
|
||||
pumpFcltN2RsrvPumpCnt: number;
|
||||
pumpFcltN3DmtrExpln: string;
|
||||
pumpFcltN3DmtrExpln: number;
|
||||
pumpFcltN3PerPpqty: number;
|
||||
pumpFcltN3PumpCnt: number;
|
||||
pumpFcltN3RsrvPumpCnt: number;
|
||||
@@ -114,17 +114,17 @@ export interface RainfallPumpStationItem {
|
||||
abstnRgnImpdQnt: number;
|
||||
grssPpqty: number;
|
||||
|
||||
pumpFcltN1DmtrExpln: string;
|
||||
pumpFcltN1DmtrExpln: number;
|
||||
pumpFcltN1PerPpqty: number;
|
||||
pumpFcltN1PumpCnt: number;
|
||||
pumpFcltN1RsrvPumpCnt: number;
|
||||
|
||||
pumpFcltN2DmtrExpln: string;
|
||||
pumpFcltN2DmtrExpln: number;
|
||||
pumpFcltN2PerPpqty: number;
|
||||
pumpFcltN2PumpCnt: number;
|
||||
pumpFcltN2RsrvPumpCnt: number;
|
||||
|
||||
pumpFcltN3DmtrExpln: string;
|
||||
pumpFcltN3DmtrExpln: number;
|
||||
pumpFcltN3PerPpqty: number;
|
||||
pumpFcltN3PumpCnt: number;
|
||||
pumpFcltN3RsrvPumpCnt: number;
|
||||
|
||||
@@ -520,6 +520,20 @@ export interface TreatWaterReuseBusinessApprStatusSubmitResponse {
|
||||
};
|
||||
}
|
||||
|
||||
export interface WaterReuseOperationDataUpdateRequest {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
sewerBzmnCd: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface WaterReuseOperationDataUpdateResponse {
|
||||
data?: string;
|
||||
message?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface StatisticsInputWaterReuseService {
|
||||
/** 공공하수처리수 재이용 조회 — ST00100001 */
|
||||
getPublicSewageTreatWaterReuseList(
|
||||
@@ -581,4 +595,8 @@ export interface StatisticsInputWaterReuseService {
|
||||
submitTreatWaterReuseBusinessApprStatus(
|
||||
request: TreatWaterReuseBusinessApprStatusSubmitRequest,
|
||||
): Promise<TreatWaterReuseBusinessApprStatusSubmitResponse>;
|
||||
/** 공공하수처리수 재이용 운영 데이터 업데이트 — ST00100001 */
|
||||
updateWaterReuseOperationData(
|
||||
request: WaterReuseOperationDataUpdateRequest,
|
||||
): Promise<WaterReuseOperationDataUpdateResponse>;
|
||||
}
|
||||
|
||||
+17
@@ -281,6 +281,20 @@ export interface PublicSewageFacilitySubmitResponse {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface PublicSewageFacilityOperationDataUpdateRequest {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
sewerBzmnCd: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface PublicSewageFacilityOperationDataUpdateResponse {
|
||||
data?: string;
|
||||
message?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface StatisticsInputPublicSewageFacilityService {
|
||||
getPublicSewageFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageFacilityResponse>;
|
||||
getPublicSewageMainFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageMainFacilityResponse>;
|
||||
@@ -288,4 +302,7 @@ export interface StatisticsInputPublicSewageFacilityService {
|
||||
saveMainFacilityMapping(param: PublicSewageMainFacilityMappingSaveRequest): Promise<PublicSewageMainFacilityMappingSaveResponse>;
|
||||
savePublicSewageFacility(param: PublicSewageFacilitySaveRequest): Promise<PublicSewageFacilitySaveResponse>;
|
||||
submitPublicSewageFacility(param: PublicSewageFacilitySubmitRequest): Promise<PublicSewageFacilitySubmitResponse>;
|
||||
updatePublicSewageFacilityOperationData(
|
||||
param: PublicSewageFacilityOperationDataUpdateRequest,
|
||||
): Promise<PublicSewageFacilityOperationDataUpdateResponse>;
|
||||
}
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
import type { StatsStatusInfo } from "./statistics-sewage-pipe-review-interface";
|
||||
|
||||
export interface ExcretaTreatmentFacilityReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
fcltNm: string;
|
||||
rowType: string;
|
||||
fcltPstnCn: string | null;
|
||||
telNo: string | null;
|
||||
cmptnStrtYmd: string | null;
|
||||
oprtngStrtYmd: string | null;
|
||||
wholPjtcoAmt: number;
|
||||
prcsFcltCpct: number;
|
||||
cpct: number;
|
||||
oprtngDayCnt: number;
|
||||
prcsMthdExpln: string | null;
|
||||
linkTmplntCn: string | null;
|
||||
linkPrcsQnt: number;
|
||||
inflwBodWqVl: number;
|
||||
inflwTocWqVl: number;
|
||||
inflwSsWqVl: number;
|
||||
inflwTnWqVl: number;
|
||||
inflwTpWqVl: number;
|
||||
inflwClfgCnt: number;
|
||||
dschgBodWqVl: number;
|
||||
dschgTocWqVl: number;
|
||||
dschgSsWqVl: number;
|
||||
dschgTnWqVl: number;
|
||||
dschgTpWqVl: number;
|
||||
dschgClfgCnt: number;
|
||||
wtsysNm: string | null;
|
||||
trbtrNm: string | null;
|
||||
msirbCn: string | null;
|
||||
ssirbCn: string | null;
|
||||
operMthdCdCn: string | null;
|
||||
wholNope: number;
|
||||
sftyAcdntEvntcsCnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
export interface ExcretaTreatmentFacilityReviewResponseData {
|
||||
excretaTreatmentFacilityList: ExcretaTreatmentFacilityReviewItem[];
|
||||
statsStatusInfo?: StatsStatusInfo;
|
||||
}
|
||||
|
||||
export interface ExcretaTreatmentFacilityReviewRequestParams {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
swtrmtFcltNm?: string;
|
||||
sewerBzmnCd?: string;
|
||||
}
|
||||
|
||||
export type ExcretaTreatmentFacilityReviewApiResponse = ApiResponse<
|
||||
ExcretaTreatmentFacilityReviewItem[] | ExcretaTreatmentFacilityReviewResponseData
|
||||
>;
|
||||
|
||||
export function extractExcretaTreatmentFacilityReviewList(
|
||||
data: ExcretaTreatmentFacilityReviewItem[] | ExcretaTreatmentFacilityReviewResponseData | undefined,
|
||||
): ExcretaTreatmentFacilityReviewItem[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
return data.excretaTreatmentFacilityList ?? [];
|
||||
}
|
||||
|
||||
export function extractExcretaTreatmentFacilityReviewStatsStatusInfo(
|
||||
data: ExcretaTreatmentFacilityReviewItem[] | ExcretaTreatmentFacilityReviewResponseData | undefined,
|
||||
): StatsStatusInfo {
|
||||
if (!data || Array.isArray(data)) return {};
|
||||
return data.statsStatusInfo ?? {};
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
import type { StatsStatusInfo } from "./statistics-sewage-pipe-review-interface";
|
||||
|
||||
export interface PrivateSewageFacilityReviewRequestParams {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
sewerBzmnCd?: string;
|
||||
}
|
||||
|
||||
/** ST00080001 검토 `data.privateSewageList[]` 한 행 */
|
||||
export interface PrivateSewageTreatFactStatusReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string | null;
|
||||
sewerBzmnCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
wholDrtwPrcsFcltCnt: number;
|
||||
wholStaaCnt: number;
|
||||
step1StaaPrcsQnt: number;
|
||||
step2StaaPrcsQnt: number;
|
||||
step3StaaPrcsQnt: number;
|
||||
step4StaaPrcsQnt: number;
|
||||
step5StaaPrcsQnt: number;
|
||||
step6StaaPrcsQnt: number;
|
||||
step7StaaPrcsQnt: number;
|
||||
staaExcpWhol: number;
|
||||
step1StaaExcpPrcsQnt: number;
|
||||
step2StaaExcpPrcsQnt: number;
|
||||
step3StaaExcpPrcsQnt: number;
|
||||
step4StaaExcpPrcsQnt: number;
|
||||
step5StaaExcpPrcsQnt: number;
|
||||
step6StaaExcpPrcsQnt: number;
|
||||
step7StaaExcpPrcsQnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00080002 검토 `data.septicTankList[]` 한 행 */
|
||||
export interface SepticTankReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string | null;
|
||||
sewerBzmnCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
grssFcltCnt: number;
|
||||
staaInsdWhol: number;
|
||||
staaInsdSttkNprsflCnt: number;
|
||||
step1StaaInsdSttkFrcCnt: number;
|
||||
staaInsdSprktpFltrtbCnt: number;
|
||||
staaInsdAertMthdCnt: number;
|
||||
staaInsdCntcAertCnt: number;
|
||||
staaInsdEtcCnt: number;
|
||||
staaInsdPrcsCpctByWhol: number;
|
||||
step1StaaInsdNope: number;
|
||||
step2StaaInsdNope: number;
|
||||
step3StaaInsdNope: number;
|
||||
step4StaaInsdNope: number;
|
||||
step5StaaInsdNope: number;
|
||||
step6StaaInsdNope: number;
|
||||
step7StaaInsdNope: number;
|
||||
step8StaaInsdNope: number;
|
||||
staaExcpWhol: number;
|
||||
staaExcpSttkNprsflCnt: number;
|
||||
step1StaaExcpSttkFrcCnt: number;
|
||||
staaExcpSprktpFltrtbCnt: number;
|
||||
staaExcpAertMthdCnt: number;
|
||||
staaExcpCntcAertCnt: number;
|
||||
staaExcpEtcCnt: number;
|
||||
staaExcpPrcsCpctByWhol: number;
|
||||
step1StaaExcpNope: number;
|
||||
step2StaaExcpNope: number;
|
||||
step3StaaExcpNope: number;
|
||||
step4StaaExcpNope: number;
|
||||
step5StaaExcpNope: number;
|
||||
step6StaaExcpNope: number;
|
||||
step7StaaExcpNope: number;
|
||||
step8StaaExcpNope: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00080003 검토 `data.mgmtAreaList[]` 한 행 */
|
||||
export interface MgmtAreaReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string | null;
|
||||
sewerBzmnCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
indvSwagePbancRgnCnt: number;
|
||||
indvSwageDsgnScpLen: number;
|
||||
spctnkGrssFcltCnt: number;
|
||||
spctnkQnt: number;
|
||||
drtwPrcsFcltGrssFcltCnt: number;
|
||||
drtwPrcsFcltQnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
export interface PrivateSewageFacilityReviewResponseData {
|
||||
privateSewageList?: PrivateSewageTreatFactStatusReviewItem[];
|
||||
septicTankList?: SepticTankReviewItem[];
|
||||
mgmtAreaList?: MgmtAreaReviewItem[];
|
||||
statsStatusInfo?: StatsStatusInfo;
|
||||
}
|
||||
|
||||
export type PrivateSewageFacilityReviewApiResponse = ApiResponse<
|
||||
unknown[] | PrivateSewageFacilityReviewResponseData
|
||||
>;
|
||||
|
||||
export function extractPrivateSewageFacilityReviewList(
|
||||
tabKey: string,
|
||||
data: unknown[] | PrivateSewageFacilityReviewResponseData | undefined,
|
||||
): unknown[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
if (tabKey === "ST00080001") {
|
||||
return data.privateSewageList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00080002") {
|
||||
return data.septicTankList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00080003") {
|
||||
return data.mgmtAreaList ?? [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function extractPrivateSewageFacilityReviewStatsStatusInfo(
|
||||
data: unknown[] | PrivateSewageFacilityReviewResponseData | undefined,
|
||||
): StatsStatusInfo {
|
||||
if (!data || Array.isArray(data)) return {};
|
||||
return data.statsStatusInfo ?? {};
|
||||
}
|
||||
|
||||
/** 검토 목록 null → 그리드 표시용 빈 문자열 */
|
||||
export function mapPrivateSewageFacilityReviewRows(list: unknown[]): object[] {
|
||||
return list.map((item) => {
|
||||
const row = item as Record<string, unknown>;
|
||||
return {
|
||||
...row,
|
||||
admdstNm: row.admdstNm ?? "",
|
||||
rmrk: row.rmrk ?? "",
|
||||
};
|
||||
});
|
||||
}
|
||||
+48
-43
@@ -1,77 +1,82 @@
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
import type { StatsStatusInfo } from "./statistics-sewage-pipe-review-interface";
|
||||
|
||||
/**
|
||||
* 05.중계펌프장 검토 아이템 인터페이스
|
||||
*/
|
||||
export interface MediationPumpStationItem {
|
||||
// 기본 정보
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstCd: string;
|
||||
statsYr: string;
|
||||
displayYear: string;
|
||||
rowType: 'OLD' | 'CURR';
|
||||
pumpstNm: string;
|
||||
impdFcltPstnCn: string;
|
||||
oprtngStrtYmd: string;
|
||||
pumpSeNm: string;
|
||||
swtrmtFcltNm: string;
|
||||
|
||||
// 제원 및 계획량
|
||||
planMltvArea: number;
|
||||
sndyArea: number;
|
||||
rainArea: number;
|
||||
exlnQnt: number;
|
||||
|
||||
// 오수펌프 (DRTW)
|
||||
drtwPumpGrssPpqty: number;
|
||||
drtwPumpN1DmtrArea: number;
|
||||
drtwPumpN1Ppqty: number;
|
||||
drtwPumpN1Cnt: number;
|
||||
drtwPumpN1RsrvCnt: number;
|
||||
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
fcltNm: string;
|
||||
pumpSeNm: string | null;
|
||||
swtrmtFcltNm: string | null;
|
||||
fcltPstnCn: string | null;
|
||||
oprtngStrtYmd: string | null;
|
||||
planMltvArea: number;
|
||||
sndyArea: number;
|
||||
rainArea: number;
|
||||
exlnQnt: number;
|
||||
drtwPumpGrssPpqty: number;
|
||||
drtwPumpN1DmtrArea: number;
|
||||
drtwPumpN1Ppqty: number;
|
||||
drtwPumpN1Cnt: number;
|
||||
drtwPumpN1RsrvCnt: number;
|
||||
drtwPumpN2DmtrArea: number;
|
||||
drtwPumpN2Ppqty: number;
|
||||
drtwPumpN2Cnt: number;
|
||||
drtwPumpN2RsrvCnt: number;
|
||||
|
||||
drtwPumpN3DmtrArea: number;
|
||||
drtwPumpN3Ppqty: number;
|
||||
drtwPumpN3Cnt: number;
|
||||
drtwPumpN3RsrvCnt: number;
|
||||
|
||||
// 우수펌프 (EXLN)
|
||||
exlnPumpGrssPpqty: number;
|
||||
exlnPumpGrssPpqty: number;
|
||||
exlnPumpN1DmtrArea: number;
|
||||
exlnPumpN1Ppqty: number;
|
||||
exlnPumpN1Cnt: number;
|
||||
exlnPumpN4RsrvCnt: number;
|
||||
|
||||
exlnPumpN1RsrvCnt: number;
|
||||
exlnPumpN2DmtrArea: number;
|
||||
exlnPumpN2Ppqty: number;
|
||||
exlnPumpN2Cnt: number;
|
||||
exlnPumpN2RsrvCnt: number;
|
||||
|
||||
exlnPumpN3DmtrArea: number;
|
||||
exlnPumpN3Ppqty: number;
|
||||
exlnPumpN3Cnt: number;
|
||||
exlnPumpN3RsrvCnt: number;
|
||||
|
||||
// 기타
|
||||
anlElpwrUseQnt: number;
|
||||
rmrk: string;
|
||||
anlElpwrUseQnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
|
||||
export interface MediationPumpStationResponseData {
|
||||
pumpStationList: MediationPumpStationItem[];
|
||||
pumpingStationList?: MediationPumpStationItem[];
|
||||
pumpStationList?: MediationPumpStationItem[];
|
||||
statsStatusInfo?: StatsStatusInfo;
|
||||
}
|
||||
|
||||
export type MediationPumpStationApiResponse = ApiResponse<
|
||||
MediationPumpStationItem[] | MediationPumpStationResponseData
|
||||
>;
|
||||
|
||||
export type MediationPumpStationApiResponse = ApiResponse<MediationPumpStationItem[]> & {
|
||||
message: string;
|
||||
};
|
||||
export function extractMediationPumpStationList(
|
||||
data: MediationPumpStationItem[] | MediationPumpStationResponseData | undefined,
|
||||
): MediationPumpStationItem[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
|
||||
const list = data.pumpingStationList ?? data.pumpStationList;
|
||||
return Array.isArray(list) ? list : [];
|
||||
}
|
||||
|
||||
export function extractMediationPumpStationStatsStatusInfo(
|
||||
data: MediationPumpStationItem[] | MediationPumpStationResponseData | undefined,
|
||||
): StatsStatusInfo {
|
||||
if (!data || Array.isArray(data)) return {};
|
||||
return data.statsStatusInfo ?? {};
|
||||
}
|
||||
|
||||
export interface MediationPumpStationService {
|
||||
getMediationPumpStationList(params: {
|
||||
@@ -81,4 +86,4 @@ export interface MediationPumpStationService {
|
||||
sggNm?: string;
|
||||
pumpstNm?: string;
|
||||
}): Promise<MediationPumpStationApiResponse>;
|
||||
}
|
||||
}
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
import type { StatsStatusInfo } from "./statistics-sewage-pipe-review-interface";
|
||||
|
||||
export interface SewageExcretaRelatedReviewRequestParams {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
sewerBzmnCd?: string;
|
||||
}
|
||||
|
||||
/** ST00120001·ST00120002 검토 `data.staffList[]` 한 행 */
|
||||
export interface StaffReviewItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
displayYear: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string | null;
|
||||
rowType: string;
|
||||
ognzNm: string | null;
|
||||
empGrssCnt: number;
|
||||
pbadmsNope: number;
|
||||
techWholNope: number;
|
||||
engrkCnt: number;
|
||||
mchnCnt: number;
|
||||
prvtmCnt: number;
|
||||
chmengCnt: number;
|
||||
envCnt: number;
|
||||
enjbEtcCnt: number;
|
||||
etcCnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00120003 검토 `data.collectionList[]` 한 행 */
|
||||
export interface CollectionReviewItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string | null;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
bzentyCnt: number;
|
||||
empNope: number;
|
||||
fcltSecrPrstCnt: number;
|
||||
n3TonUdrCnt: number;
|
||||
n4TonUdrCnt: number;
|
||||
n8TonUdrCnt: number;
|
||||
etcCnt: number;
|
||||
clctCnvncPrfmncQnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00120004 검토 `data.businessList[]` 한 행 */
|
||||
export interface BusinessReviewItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string | null;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
designMdsrCnt: number;
|
||||
designMdsrNope: number;
|
||||
mfbizCnt: number;
|
||||
mfbizMdsrNope: number;
|
||||
mngCnt: number;
|
||||
mngMdsrNope: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
export interface SewageExcretaRelatedReviewResponseData {
|
||||
staffList?: StaffReviewItem[];
|
||||
collectionList?: CollectionReviewItem[];
|
||||
businessList?: BusinessReviewItem[];
|
||||
statsStatusInfo?: StatsStatusInfo;
|
||||
}
|
||||
|
||||
export type SewageExcretaRelatedReviewApiResponse = ApiResponse<
|
||||
unknown[] | SewageExcretaRelatedReviewResponseData
|
||||
>;
|
||||
|
||||
export function extractSewageExcretaRelatedReviewList(
|
||||
tabKey: string,
|
||||
data: unknown[] | SewageExcretaRelatedReviewResponseData | undefined,
|
||||
): unknown[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
if (tabKey === "ST00120001" || tabKey === "ST00120002") {
|
||||
return data.staffList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00120003") {
|
||||
return data.collectionList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00120004") {
|
||||
return data.businessList ?? [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function extractSewageExcretaRelatedReviewStatsStatusInfo(
|
||||
data: unknown[] | SewageExcretaRelatedReviewResponseData | undefined,
|
||||
): StatsStatusInfo {
|
||||
if (!data || Array.isArray(data)) return {};
|
||||
return data.statsStatusInfo ?? {};
|
||||
}
|
||||
|
||||
export function mapSewageExcretaRelatedReviewRows(list: unknown[]): object[] {
|
||||
return list.map((item) => {
|
||||
const row = item as Record<string, unknown>;
|
||||
return {
|
||||
...row,
|
||||
admdstNm: row.admdstNm ?? "",
|
||||
ognzNm: row.ognzNm ?? "",
|
||||
rmrk: row.rmrk ?? "",
|
||||
};
|
||||
});
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
import type { StatsStatusInfo } from "./statistics-sewage-pipe-review-interface";
|
||||
|
||||
export interface SewageExcretaSlurgeReviewRequestParams {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
sewerBzmnCd?: string;
|
||||
swtrmtFcltNm?: string;
|
||||
}
|
||||
|
||||
/** ST00110001 검토 `data.sludgeProcessingList[]` 한 행 */
|
||||
export interface SludgeProcessingReviewItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
swtrmtFcltNm: string | null;
|
||||
gnamWhol: number;
|
||||
itslfQnt: number;
|
||||
otsdInflwQnt: number;
|
||||
transfTmplntNm: string | null;
|
||||
transfPrcsQnt: number;
|
||||
srpFncRt: number;
|
||||
wholSrpPrcsQnt: number;
|
||||
itslfTs1Whol: number;
|
||||
itslfTs1RclmQnt: number;
|
||||
itslfTs1IncnrQnt: number;
|
||||
incnrPrcsWhol: number;
|
||||
incnrPrcsLfvlm: number;
|
||||
incnrPrcsPrdctRmtrlQnt: number;
|
||||
incnrPrcsFmlyrQnt: number;
|
||||
incnrPrcsFcltNm: string | null;
|
||||
itslfTs1AltdQnt: number;
|
||||
stbznPrcsWhol: number;
|
||||
stbznPrcsLfvlm: number;
|
||||
stbznPrcsFmlyrQnt: number;
|
||||
stbznPrcsFcltNm: string | null;
|
||||
itslfTs1DryQnt: number;
|
||||
dryPrcsWhol: number;
|
||||
dryPrcsLfvlm: number;
|
||||
dryPrcsIncnrQnt: number;
|
||||
dryPrcsFuelQnt: number;
|
||||
dryPrcsPrdctRmtrlQnt: number;
|
||||
dryPrcsFmlyrQnt: number;
|
||||
dryPrcsCmpstgQnt: number;
|
||||
dryPrcsFcltNm: string | null;
|
||||
itslfTs1FuelQnt: number;
|
||||
fuelPrcsWhol: number;
|
||||
fuelPrcsIncnrQnt: number;
|
||||
fuelPrcsFuelQnt: number;
|
||||
fuelPrcsFcltNm: string | null;
|
||||
itslfTs1CmpstgQnt: number;
|
||||
cmpsPrcsFcltBzentyNm: string | null;
|
||||
otsdCnsgnWhol: number;
|
||||
otsdCnsgnRclmQnt: number;
|
||||
otsdCnsgnIncnrQnt: number;
|
||||
otsdCnsgnFuelPwplQnt: number;
|
||||
otsdCnsgnPrdctRmtrlQnt: number;
|
||||
otsdCnsgnFmlyrQnt: number;
|
||||
otsdCnsgnCmpstgQnt: number;
|
||||
otsdCnsgnFcltNm: string | null;
|
||||
cparTs2Whol: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00110002 검토 `data.digestionTankOperList[]` 한 행 */
|
||||
export interface DigestionTankOperReviewItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
swtrmtFcltNm: string | null;
|
||||
digstnEnYn: string | null;
|
||||
digstnCpct: number;
|
||||
actlDrvnCpct: number;
|
||||
digstnGasInputQnt: number;
|
||||
digstnGasInputSldDnst: number;
|
||||
digstnTnkOprtngDayCnt: number;
|
||||
ocrnGasQnt: number;
|
||||
digstnGasUtztnWholPrst: number;
|
||||
digstnGasUtztnNtslQnt: number;
|
||||
digstnGasUtztnEtgtQnt: number;
|
||||
digstnGasUtztnItslfQnt: number;
|
||||
digstnGasUtztnEtcQnt: number;
|
||||
surplsGasQnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00110003 검토 `data.sewageSludgeOperList[]` 한 행 */
|
||||
export interface SewageSludgeOperReviewItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
fcltNm: string | null;
|
||||
prcsMthExpln: string | null;
|
||||
fcltCpct: number | null;
|
||||
anlPrcsQnt: number | null;
|
||||
dayAvgOprtngHr: number | null;
|
||||
anlOprtngDayCnt: number | null;
|
||||
oprtngRt: number | null;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00110004 검토 `data.humanWasteSludgeList[]` 한 행 */
|
||||
export interface HumanWasteSludgeReviewItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
fcltNm: string | null;
|
||||
gnam: number | null;
|
||||
wholDspsQnt: number | null;
|
||||
rcycQnt: number | null;
|
||||
olandLfvlm: number | null;
|
||||
incnrQnt: number | null;
|
||||
oceanInputQnt: number | null;
|
||||
fuelQnt: number | null;
|
||||
etcQnt: number | null;
|
||||
nondspsQnt: number | null;
|
||||
swtrmtFcltNm: string | null;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
export interface SewageExcretaSlurgeReviewResponseData {
|
||||
sludgeProcessingList?: SludgeProcessingReviewItem[];
|
||||
digestionTankOperList?: DigestionTankOperReviewItem[];
|
||||
sewageSludgeOperList?: SewageSludgeOperReviewItem[];
|
||||
humanWasteSludgeList?: HumanWasteSludgeReviewItem[];
|
||||
statsStatusInfo?: StatsStatusInfo;
|
||||
}
|
||||
|
||||
export type SewageExcretaSlurgeReviewApiResponse = ApiResponse<
|
||||
unknown[] | SewageExcretaSlurgeReviewResponseData
|
||||
>;
|
||||
|
||||
export function extractSewageExcretaSlurgeReviewList(
|
||||
tabKey: string,
|
||||
data: unknown[] | SewageExcretaSlurgeReviewResponseData | undefined,
|
||||
): unknown[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
if (tabKey === "ST00110001") {
|
||||
return data.sludgeProcessingList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00110002") {
|
||||
return data.digestionTankOperList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00110003") {
|
||||
return data.sewageSludgeOperList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00110004") {
|
||||
return data.humanWasteSludgeList ?? [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function extractSewageExcretaSlurgeReviewStatsStatusInfo(
|
||||
data: unknown[] | SewageExcretaSlurgeReviewResponseData | undefined,
|
||||
): StatsStatusInfo {
|
||||
if (!data || Array.isArray(data)) return {};
|
||||
return data.statsStatusInfo ?? {};
|
||||
}
|
||||
|
||||
export function mapSewageExcretaSlurgeReviewRows(list: unknown[]): object[] {
|
||||
return list.map((item) => {
|
||||
const row = item as Record<string, unknown>;
|
||||
const isHumanWasteSludgeRow = "gnam" in row;
|
||||
return {
|
||||
...row,
|
||||
admdstNm: row.admdstNm ?? "",
|
||||
fcltNm: isHumanWasteSludgeRow
|
||||
? (row.fcltNm ?? "")
|
||||
: (row.fcltNm ?? row.swtrmtFcltNm ?? ""),
|
||||
swtrmtFcltNm: row.swtrmtFcltNm ?? "",
|
||||
transfTmplntNm: row.transfTmplntNm ?? "",
|
||||
incnrPrcsFcltNm: row.incnrPrcsFcltNm ?? "",
|
||||
stbznPrcsFcltNm: row.stbznPrcsFcltNm ?? "",
|
||||
dryPrcsFcltNm: row.dryPrcsFcltNm ?? "",
|
||||
fuelPrcsFcltNm: row.fuelPrcsFcltNm ?? "",
|
||||
cmpsPrcsFcltBzentyNm: row.cmpsPrcsFcltBzentyNm ?? "",
|
||||
otsdCnsgnFcltNm: row.otsdCnsgnFcltNm ?? "",
|
||||
// ST00110002 — API 필드 → review 그리드 컬럼 fieldName
|
||||
digesterYn: row.digesterYn ?? row.digstnEnYn ?? "",
|
||||
dsgnCpct: row.dsgnCpct ?? row.digstnCpct ?? 0,
|
||||
actlOperCpct: row.actlOperCpct ?? row.actlDrvnCpct ?? 0,
|
||||
inputQnt: row.inputQnt ?? row.digstnGasInputQnt ?? 0,
|
||||
tsConc: row.tsConc ?? row.digstnGasInputSldDnst ?? 0,
|
||||
oprtngDays: row.oprtngDays ?? row.digstnTnkOprtngDayCnt ?? 0,
|
||||
gnrtGasQnt: row.gnrtGasQnt ?? row.ocrnGasQnt ?? 0,
|
||||
gasTotal: row.gasTotal ?? row.digstnGasUtztnWholPrst ?? 0,
|
||||
gasSale: row.gasSale ?? row.digstnGasUtztnNtslQnt ?? 0,
|
||||
gasGen: row.gasGen ?? row.digstnGasUtztnEtgtQnt ?? 0,
|
||||
gasSelf: row.gasSelf ?? row.digstnGasUtztnItslfQnt ?? 0,
|
||||
gasEtc: row.gasEtc ?? row.digstnGasUtztnEtcQnt ?? 0,
|
||||
surplusGas: row.surplusGas ?? row.surplsGasQnt ?? 0,
|
||||
prcsMthExpln: row.prcsMthExpln ?? "",
|
||||
fcltCpct: row.fcltCpct ?? 0,
|
||||
anlPrcsQnt: row.anlPrcsQnt ?? 0,
|
||||
dayAvgOprtngHr: row.dayAvgOprtngHr ?? 0,
|
||||
anlOprtngDayCnt: row.anlOprtngDayCnt ?? 0,
|
||||
oprtngRt: row.oprtngRt ?? 0,
|
||||
// ST00110004
|
||||
gnam: row.gnam ?? 0,
|
||||
wholDspsQnt: row.wholDspsQnt ?? 0,
|
||||
rcycQnt: row.rcycQnt ?? 0,
|
||||
olandLfvlm: row.olandLfvlm ?? 0,
|
||||
incnrQnt: row.incnrQnt ?? 0,
|
||||
oceanInputQnt: row.oceanInputQnt ?? 0,
|
||||
fuelQnt: row.fuelQnt ?? 0,
|
||||
etcQnt: row.etcQnt ?? 0,
|
||||
nondspsQnt: row.nondspsQnt ?? 0,
|
||||
rmrk: row.rmrk ?? "",
|
||||
};
|
||||
});
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
import type { StatsStatusInfo } from "./statistics-sewage-pipe-review-interface";
|
||||
|
||||
export interface SewerageFinanceReviewRequestParams {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
sewerBzmnCd?: string;
|
||||
}
|
||||
|
||||
/** ST00090001 검토 `data.financialSettlementList[]` 한 행 */
|
||||
export interface FinancialSettlementReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
fnnByWholPjtcoAmt: number;
|
||||
cnterGovSprtAmt: number;
|
||||
mctpvLcexpAmt: number;
|
||||
wareaSggLclAmt: number;
|
||||
sewerUseAmt: number;
|
||||
orgprBrdnAmt: number;
|
||||
etcIncmAmt: number;
|
||||
crfwAmt: number;
|
||||
lbltAmt: number;
|
||||
usgByWholPjtcoAmt: number;
|
||||
tmplntWholAmt: number;
|
||||
tmplntFcltAmt: number;
|
||||
tmplntMendngAmt: number;
|
||||
tmplntOperKeepAmt: number;
|
||||
tmplntEtcAmt: number;
|
||||
wholSwagePplAmt: number;
|
||||
swagePplFcltAmt: number;
|
||||
swagePplMendngAmt: number;
|
||||
swagePplOperKeepAmt: number;
|
||||
swagePplEtcAmt: number;
|
||||
wholSwageImpdFcltAmt: number;
|
||||
swageImpdFcltAmt: number;
|
||||
swageImpdMendngAmt: number;
|
||||
swageImpdOperKeepAmt: number;
|
||||
swageImpdFcltEtcAmt: number;
|
||||
etcWholAmt: number;
|
||||
etcAcdntCrfwAmt: number;
|
||||
etcNrcpAmt: number;
|
||||
etcAmt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00090002 검토 `data.sewerageFeeList[]` 한 행 */
|
||||
export interface SewerageFeeReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
anlLevyQnt: number;
|
||||
levyAmt: number;
|
||||
avgUntprcAmt: number;
|
||||
grssPrmcoAmt: number;
|
||||
grssUnitPrmcoAmt: number;
|
||||
rlztnRt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00090003 검토 `data.usageFeeList[]` 한 행 */
|
||||
export interface UsageFeeReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
levyAmt: number;
|
||||
levyQnt: number;
|
||||
hshusLevyAmt: number;
|
||||
hshusLevyQnt: number;
|
||||
hshusUntprcAmt: number;
|
||||
taskFrLevyAmt: number;
|
||||
taskFrLevyQnt: number;
|
||||
taskFrUntprcAmt: number;
|
||||
bsnGnrlLevyAmt: number;
|
||||
bsnGnrlLevyQnt: number;
|
||||
bsnGnrlUntprcAmt: number;
|
||||
massBthhsFrLevyAmt: number;
|
||||
massBthhsFrLevyQnt: number;
|
||||
massBthhsFrUntprcAmt: number;
|
||||
bthhsFrSpcs2LevyAmt: number;
|
||||
bthhsFrSpcs2LevyQnt: number;
|
||||
bthhsFrSpcs2UntprcAmt: number;
|
||||
induMdsrLevyAmt: number;
|
||||
induMdsrLevyQnt: number;
|
||||
induMdsrUntprcAmt: number;
|
||||
etcLevyAmt: number;
|
||||
etcLevyQnt: number;
|
||||
etcLevyUntprcAmt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00090004 검토 `data.orgCauseList[]` 한 행 */
|
||||
export interface OrgCauseReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
levyEvntcsCnt: number;
|
||||
levyAmt: number;
|
||||
trgtByWholClctnEvntcsCnt: number;
|
||||
trgtByWholClctnAmt: number;
|
||||
mltvEqpmEvntcsCnt: number;
|
||||
mltvEqpmAmt: number;
|
||||
othrCstrnOthrActrCnt: number;
|
||||
othrCstrnOthrActrAmt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
export interface SewerageFinanceReviewResponseData {
|
||||
financialSettlementList?: FinancialSettlementReviewItem[];
|
||||
sewerageFeeList?: SewerageFeeReviewItem[];
|
||||
usageFeeList?: UsageFeeReviewItem[];
|
||||
orgCauseList?: OrgCauseReviewItem[];
|
||||
statsStatusInfo?: StatsStatusInfo;
|
||||
}
|
||||
|
||||
export type SewerageFinanceReviewApiResponse = ApiResponse<
|
||||
unknown[] | SewerageFinanceReviewResponseData
|
||||
>;
|
||||
|
||||
export function extractSewerageFinanceReviewList(
|
||||
tabKey: string,
|
||||
data: unknown[] | SewerageFinanceReviewResponseData | undefined,
|
||||
): unknown[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
if (tabKey === "ST00090001") {
|
||||
return data.financialSettlementList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00090002") {
|
||||
return data.sewerageFeeList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00090003") {
|
||||
return data.usageFeeList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00090004") {
|
||||
return data.orgCauseList ?? [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function extractSewerageFinanceReviewStatsStatusInfo(
|
||||
data: unknown[] | SewerageFinanceReviewResponseData | undefined,
|
||||
): StatsStatusInfo {
|
||||
if (!data || Array.isArray(data)) return {};
|
||||
return data.statsStatusInfo ?? {};
|
||||
}
|
||||
|
||||
export function mapSewerageFinanceReviewRows(list: unknown[]): object[] {
|
||||
return list.map((item) => {
|
||||
const row = item as Record<string, unknown>;
|
||||
return {
|
||||
...row,
|
||||
admdstNm: row.admdstNm ?? "",
|
||||
rmrk: row.rmrk ?? "",
|
||||
};
|
||||
});
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
import type { StatsStatusInfo } from "./statistics-sewage-pipe-review-interface";
|
||||
|
||||
export interface SewerageServiceLevelReviewRequestParams {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
sewerBzmnCd?: string;
|
||||
swtrmtFcltNm?: string;
|
||||
}
|
||||
|
||||
/** ST00130001 검토 `data.complaintList[]` 한 행 */
|
||||
export interface ComplaintReviewItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string | null;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
swtrmtPpltnCnt: number;
|
||||
whol: number;
|
||||
odorCnt: number;
|
||||
swagePplCnt: number;
|
||||
dschgWqCnt: number;
|
||||
ldsCnt: number;
|
||||
plltnAcdntCnt: number;
|
||||
sewerCrgAmt: number;
|
||||
etcCnt: number;
|
||||
crtrPpltnCvlcptEvntcsCnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00130002 검토 `data.facilityList[]` 한 행 */
|
||||
export interface ResidentFriendlyFacilityReviewItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string | null;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
swtrmtFcltNm: string | null;
|
||||
fcltCpct: number;
|
||||
tmplntArea: number;
|
||||
whol: number;
|
||||
brkFcltArea: number;
|
||||
childFrshpSpacArea: number;
|
||||
bdySportsFcltArea: number;
|
||||
sportsFcltArea: number;
|
||||
pkltArea: number;
|
||||
eduMultiFcltArea: number;
|
||||
wtlndMakeArea: number;
|
||||
etcCnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
export interface SewerageServiceLevelReviewResponseData {
|
||||
complaintList?: ComplaintReviewItem[];
|
||||
facilityList?: ResidentFriendlyFacilityReviewItem[];
|
||||
statsStatusInfo?: StatsStatusInfo;
|
||||
}
|
||||
|
||||
export type SewerageServiceLevelReviewApiResponse = ApiResponse<
|
||||
unknown[] | SewerageServiceLevelReviewResponseData
|
||||
>;
|
||||
|
||||
export function extractSewerageServiceLevelReviewList(
|
||||
tabKey: string,
|
||||
data: unknown[] | SewerageServiceLevelReviewResponseData | undefined,
|
||||
): unknown[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
if (tabKey === "ST00130001") {
|
||||
return data.complaintList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00130002") {
|
||||
return data.facilityList ?? [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function extractSewerageServiceLevelReviewStatsStatusInfo(
|
||||
data: unknown[] | SewerageServiceLevelReviewResponseData | undefined,
|
||||
): StatsStatusInfo {
|
||||
if (!data || Array.isArray(data)) return {};
|
||||
return data.statsStatusInfo ?? {};
|
||||
}
|
||||
|
||||
export function mapSewerageServiceLevelReviewRows(list: unknown[]): object[] {
|
||||
return list.map((item) => {
|
||||
const row = item as Record<string, unknown>;
|
||||
return {
|
||||
...row,
|
||||
rowType: row.rowType ?? "",
|
||||
admdstNm: row.admdstNm ?? "",
|
||||
swtrmtFcltNm: row.swtrmtFcltNm ?? "",
|
||||
rmrk: row.rmrk ?? "",
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
import type { StatsStatusInfo } from "./statistics-sewage-pipe-review-interface";
|
||||
|
||||
export interface WaterReuseReviewRequestParams {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
sewerBzmnCd?: string;
|
||||
swtrmtFcltNm?: string;
|
||||
}
|
||||
|
||||
/** ST00100001 검토 `data.reuseStatusList[]` 한 행 */
|
||||
export interface ReuseStatusReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
swtrmtFcltNm: string;
|
||||
sncnYmd: string | null;
|
||||
prcsFcltCpct: number | string;
|
||||
prcsQnt: number;
|
||||
lglYn: string | null;
|
||||
instlYn: string | null;
|
||||
oprtngStrtYmd: string | null;
|
||||
instlAprvYmd: string | null;
|
||||
rprcsFcltCpct: number;
|
||||
mtcnsNm: string | null;
|
||||
ruswtPplLen: number;
|
||||
ruswtPumpLen: number;
|
||||
wholUseQnt: number;
|
||||
ithlWshwtrQnt: number;
|
||||
ithlClnQty: number;
|
||||
ithlFrzgQty: number;
|
||||
ithlLdhcUswtrQnt: number;
|
||||
ithlDltnUswtrQnt: number;
|
||||
ithlRuswtrQnt: number;
|
||||
ithlEtcQnt: number;
|
||||
ithlUnsdQnt: number;
|
||||
otcntrUswtrWhol: number;
|
||||
otcntrClnRstrmQnt: number;
|
||||
otcntrWshwtrQnt: number;
|
||||
otcntrLdhcUswtrQnt: number;
|
||||
otcntrHdrplUswtrQnt: number;
|
||||
otcntrRvrKeepQnt: number;
|
||||
agrcltUswtrPstnQnt: number;
|
||||
ugwtrChargQnt: number;
|
||||
mdsrUswtrQnt: number;
|
||||
rtlztnRt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00100002 검토 `data.grayWaterList[]` 한 행 */
|
||||
export interface GrayWaterReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
fcltNm: string;
|
||||
fcltPstnCn: string | null;
|
||||
bldgSeCn: string | null;
|
||||
lglTrgtYn: string | null;
|
||||
cmptnStrtYmd: string | null;
|
||||
oprtngStrtYmd: string | null;
|
||||
archWholArea: number;
|
||||
prcsCpct: number;
|
||||
wtrUseQnt: number;
|
||||
ruswtrUtztnQnt: number;
|
||||
sbstCntUtztnQnt: number;
|
||||
mnUsgCn: string | null;
|
||||
ruswtrOperAmt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00100003 검토 `data.rainwaterList[]` 한 행 */
|
||||
export interface RainwaterReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
fcltNm: string;
|
||||
bdstTypCn: string | null;
|
||||
fcltPstnCn: string | null;
|
||||
lglTrgtYn: string | null;
|
||||
instlCmptnYmd: string | null;
|
||||
instlAmt: number;
|
||||
rcptMynCn: string | null;
|
||||
filtrPrcsFcltYnExpln: string | null;
|
||||
impdCpct: number;
|
||||
anlUseQnt: number;
|
||||
exlnUtlzUsgExpln: string | null;
|
||||
anlOperAmt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00100004 검토 `data.regStatusList[]` 한 행 */
|
||||
export interface RegStatusReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
bplcNm: string | null;
|
||||
rprsvNm: string | null;
|
||||
bplcPstnCn: string | null;
|
||||
regDt: string | null;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
/** ST00100005 검토 `data.sncnStatusList[]` 한 행 */
|
||||
export interface SncnStatusReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstNm: string | null;
|
||||
admdstCd: string;
|
||||
displayYear: string;
|
||||
rowType: string;
|
||||
bplcNm: string | null;
|
||||
rprsvNm: string | null;
|
||||
bplcPstnCn: string | null;
|
||||
coRegYmd: string | null;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
export interface WaterReuseReviewResponseData {
|
||||
reuseStatusList?: ReuseStatusReviewItem[];
|
||||
grayWaterList?: GrayWaterReviewItem[];
|
||||
rainwaterList?: RainwaterReviewItem[];
|
||||
regStatusList?: RegStatusReviewItem[];
|
||||
sncnStatusList?: SncnStatusReviewItem[];
|
||||
statsStatusInfo?: StatsStatusInfo;
|
||||
}
|
||||
|
||||
export type WaterReuseReviewApiResponse = ApiResponse<
|
||||
unknown[] | WaterReuseReviewResponseData
|
||||
>;
|
||||
|
||||
export function extractWaterReuseReviewList(
|
||||
tabKey: string,
|
||||
data: unknown[] | WaterReuseReviewResponseData | undefined,
|
||||
): unknown[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
if (tabKey === "ST00100001") {
|
||||
return data.reuseStatusList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00100002") {
|
||||
return data.grayWaterList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00100003") {
|
||||
return data.rainwaterList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00100004") {
|
||||
return data.regStatusList ?? [];
|
||||
}
|
||||
if (tabKey === "ST00100005") {
|
||||
return data.sncnStatusList ?? [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function extractWaterReuseReviewStatsStatusInfo(
|
||||
data: unknown[] | WaterReuseReviewResponseData | undefined,
|
||||
): StatsStatusInfo {
|
||||
if (!data || Array.isArray(data)) return {};
|
||||
return data.statsStatusInfo ?? {};
|
||||
}
|
||||
|
||||
export function mapWaterReuseReviewRows(list: unknown[]): object[] {
|
||||
return list.map((item) => {
|
||||
const row = item as Record<string, unknown>;
|
||||
return {
|
||||
...row,
|
||||
admdstNm: row.admdstNm ?? "",
|
||||
swtrmtFcltNm: row.swtrmtFcltNm ?? "",
|
||||
sncnYmd: row.sncnYmd ?? "",
|
||||
lglYn: row.lglYn ?? "",
|
||||
instlYn: row.instlYn ?? "",
|
||||
oprtngStrtYmd: row.oprtngStrtYmd ?? "",
|
||||
instlAprvYmd: row.instlAprvYmd ?? "",
|
||||
mtcnsNm: row.mtcnsNm ?? "",
|
||||
fcltNm: row.fcltNm ?? "",
|
||||
fcltPstnCn: row.fcltPstnCn ?? "",
|
||||
bldgSeCn: row.bldgSeCn ?? "",
|
||||
lglTrgtYn: row.lglTrgtYn ?? "",
|
||||
cmptnStrtYmd: row.cmptnStrtYmd ?? "",
|
||||
bdstTypCn: row.bdstTypCn ?? "",
|
||||
instlCmptnYmd: row.instlCmptnYmd ?? "",
|
||||
rcptMynCn: row.rcptMynCn ?? "",
|
||||
filtrPrcsFcltYnExpln: row.filtrPrcsFcltYnExpln ?? "",
|
||||
exlnUtlzUsgExpln: row.exlnUtlzUsgExpln ?? "",
|
||||
bplcNm: row.bplcNm ?? "",
|
||||
rprsvNm: row.rprsvNm ?? "",
|
||||
bplcPstnCn: row.bplcPstnCn ?? "",
|
||||
regDt: row.regDt ?? "",
|
||||
coRegYmd: row.coRegYmd ?? "",
|
||||
mnUsgCn: row.mnUsgCn ?? "",
|
||||
rmrk: row.rmrk ?? "",
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -73,9 +73,11 @@ const StatisticsPage = () => {
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() =>
|
||||
import(
|
||||
`@/components/statistics/${category}/${resolvedComponentName}`
|
||||
),
|
||||
category
|
||||
? import(
|
||||
`@/components/statistics/${category}/${resolvedComponentName}`
|
||||
)
|
||||
: import(`@/components/statistics/${resolvedComponentName}`),
|
||||
{ ssr: false, loading: () => <SkeletonPage /> },
|
||||
);
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export function resolveStatisticsTabId(urlPath: string): string {
|
||||
return componentName || rawUrlPath;
|
||||
}
|
||||
|
||||
/** `@/components/statistics/{category}/{Component}` 경로 */
|
||||
/** `@/components/statistics/{category}/{Component}` 또는 루트 컴포넌트 경로 */
|
||||
export function buildStatisticsComponentImportPath(
|
||||
urlPath: string,
|
||||
parentEngNm: string,
|
||||
@@ -63,6 +63,9 @@ export function buildStatisticsComponentImportPath(
|
||||
const { componentName } = parseStatisticsMenuUrlPath(urlPath);
|
||||
const category = resolveStatisticsCategory(urlPath, parentEngNm);
|
||||
const name = componentName || urlPath;
|
||||
if (!category) {
|
||||
return `@/components/statistics/${name}`;
|
||||
}
|
||||
return `@/components/statistics/${category}/${name}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
type TabContextMenuState = {
|
||||
x: number;
|
||||
y: number;
|
||||
tabId: string;
|
||||
};
|
||||
|
||||
const TabBar = () => {
|
||||
const { tabs, activeTabId, setActiveTab, removeTab } = useTabStore();
|
||||
const {
|
||||
tabs,
|
||||
activeTabId,
|
||||
setActiveTab,
|
||||
removeTab,
|
||||
closeAllTabs,
|
||||
closeOtherTabs,
|
||||
closeTabsToLeft,
|
||||
closeTabsToRight,
|
||||
} = useTabStore();
|
||||
const [contextMenu, setContextMenu] = useState<TabContextMenuState | null>(null);
|
||||
const contextMenuRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const closeContextMenu = useCallback(() => {
|
||||
setContextMenu(null);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!contextMenu) return;
|
||||
|
||||
const onPointerDown = (event: PointerEvent) => {
|
||||
if (contextMenuRef.current?.contains(event.target as Node)) return;
|
||||
closeContextMenu();
|
||||
};
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") closeContextMenu();
|
||||
};
|
||||
const onScroll = () => closeContextMenu();
|
||||
|
||||
window.addEventListener("pointerdown", onPointerDown);
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
window.addEventListener("scroll", onScroll, true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("pointerdown", onPointerDown);
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
window.removeEventListener("scroll", onScroll, true);
|
||||
};
|
||||
}, [closeContextMenu, contextMenu]);
|
||||
|
||||
if (tabs.length === 0) {
|
||||
return null;
|
||||
@@ -18,36 +65,128 @@ const TabBar = () => {
|
||||
removeTab(tabId);
|
||||
};
|
||||
|
||||
const handleTabContextMenu = (
|
||||
e: React.MouseEvent<HTMLButtonElement>,
|
||||
tabId: string,
|
||||
) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setContextMenu({ x: e.clientX, y: e.clientY, tabId });
|
||||
};
|
||||
|
||||
const handleCloseOtherTabs = () => {
|
||||
if (!contextMenu || tabs.length <= 1) return;
|
||||
closeOtherTabs(contextMenu.tabId);
|
||||
closeContextMenu();
|
||||
};
|
||||
|
||||
const handleCloseAllTabs = () => {
|
||||
closeAllTabs();
|
||||
closeContextMenu();
|
||||
};
|
||||
|
||||
const contextTabIndex = contextMenu
|
||||
? tabs.findIndex((tab) => tab.id === contextMenu.tabId)
|
||||
: -1;
|
||||
const canCloseTabsToLeft = contextTabIndex > 0;
|
||||
const canCloseTabsToRight =
|
||||
contextTabIndex >= 0 && contextTabIndex < tabs.length - 1;
|
||||
const canCloseOtherTabs = tabs.length > 1;
|
||||
|
||||
const handleCloseTabsToLeft = () => {
|
||||
if (!contextMenu || !canCloseTabsToLeft) return;
|
||||
closeTabsToLeft(contextMenu.tabId);
|
||||
closeContextMenu();
|
||||
};
|
||||
|
||||
const handleCloseTabsToRight = () => {
|
||||
if (!contextMenu || !canCloseTabsToRight) return;
|
||||
closeTabsToRight(contextMenu.tabId);
|
||||
closeContextMenu();
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
value={activeTabId || undefined}
|
||||
onValueChange={setActiveTab}
|
||||
className="w-full border-b border-border"
|
||||
variant="outline"
|
||||
>
|
||||
<TabsList className="w-full !justify-start !p-4 !pb-0">
|
||||
{tabs.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
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">
|
||||
{tab.label}
|
||||
</span>
|
||||
<span
|
||||
role="button"
|
||||
onClick={(e) => handleCloseTab(e, tab.id)}
|
||||
aria-label={`${tab.label} 탭 닫기`}
|
||||
className="flex flex-shrink-0 items-center justify-center leading-none hover:text-[#2094fa]"
|
||||
<>
|
||||
<Tabs
|
||||
value={activeTabId || undefined}
|
||||
onValueChange={setActiveTab}
|
||||
className="w-full border-b border-border"
|
||||
variant="outline"
|
||||
>
|
||||
<TabsList className="w-full !justify-start !p-4 !pb-0">
|
||||
{tabs.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
className="!flex !w-[140px] !items-center !justify-start !gap-2 !px-4 [&::after]:hidden"
|
||||
title={tab.label}
|
||||
onContextMenu={(e) => handleTabContextMenu(e, tab.id)}
|
||||
>
|
||||
<i className="ri-close-line" />
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<span className="flex-1 truncate text-xl leading-none">
|
||||
{tab.label}
|
||||
</span>
|
||||
<span
|
||||
role="button"
|
||||
onClick={(e) => handleCloseTab(e, tab.id)}
|
||||
aria-label={`${tab.label} 탭 닫기`}
|
||||
className="flex flex-shrink-0 items-center justify-center leading-none hover:text-[#2094fa]"
|
||||
>
|
||||
<i className="ri-close-line" />
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
{contextMenu &&
|
||||
typeof document !== "undefined" &&
|
||||
createPortal(
|
||||
<div
|
||||
ref={contextMenuRef}
|
||||
role="menu"
|
||||
className="fixed z-[100] min-w-[20rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"
|
||||
style={{ left: contextMenu.x, top: contextMenu.y }}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
disabled={!canCloseTabsToLeft}
|
||||
className="flex w-full cursor-default select-none items-center rounded-sm px-4 py-3 text-left text-[1.2rem] outline-none transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"
|
||||
onClick={handleCloseTabsToLeft}
|
||||
>
|
||||
왼쪽 탭 모두 닫기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
disabled={!canCloseTabsToRight}
|
||||
className="flex w-full cursor-default select-none items-center rounded-sm px-4 py-3 text-left text-[1.2rem] outline-none transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"
|
||||
onClick={handleCloseTabsToRight}
|
||||
>
|
||||
오른쪽 탭 모두 닫기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
disabled={!canCloseOtherTabs}
|
||||
className="flex w-full cursor-default select-none items-center rounded-sm px-4 py-3 text-left text-[1.2rem] outline-none transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"
|
||||
onClick={handleCloseOtherTabs}
|
||||
>
|
||||
해당 탭 제외 모두 닫기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className="flex w-full cursor-default select-none items-center rounded-sm px-4 py-3 text-left text-[1.2rem] outline-none transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||
onClick={handleCloseAllTabs}
|
||||
>
|
||||
모든 탭 닫기
|
||||
</button>
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -89,6 +89,8 @@ interface TabStore {
|
||||
setMenuList: (menuList: MenuItem[] | null) => void;
|
||||
closeAllTabs: () => void;
|
||||
closeOtherTabs: (tabId: string) => void;
|
||||
closeTabsToLeft: (tabId: string) => void;
|
||||
closeTabsToRight: (tabId: string) => void;
|
||||
}
|
||||
|
||||
export const TAB_STORE_PERSIST_KEY = "st-tab-store";
|
||||
@@ -240,6 +242,46 @@ const useTabStoreHook = create<TabStore>()(
|
||||
});
|
||||
}
|
||||
},
|
||||
closeTabsToLeft: (tabId: string) => {
|
||||
const { tabs, activeTabId } = get();
|
||||
const index = tabs.findIndex((tab) => tab.id === tabId);
|
||||
if (index <= 0) return;
|
||||
|
||||
const newTabs = tabs.slice(index);
|
||||
let newActiveTabId = activeTabId;
|
||||
if (activeTabId && !newTabs.some((tab) => tab.id === activeTabId)) {
|
||||
newActiveTabId = tabId;
|
||||
}
|
||||
|
||||
set({
|
||||
tabs: newTabs,
|
||||
activeTabId: newActiveTabId,
|
||||
});
|
||||
if (newActiveTabId) {
|
||||
const nextActive = get().tabs.find((t) => t.id === newActiveTabId);
|
||||
if (nextActive) ensureTabPageMenuAuthority(get, set, nextActive);
|
||||
}
|
||||
},
|
||||
closeTabsToRight: (tabId: string) => {
|
||||
const { tabs, activeTabId } = get();
|
||||
const index = tabs.findIndex((tab) => tab.id === tabId);
|
||||
if (index === -1 || index >= tabs.length - 1) return;
|
||||
|
||||
const newTabs = tabs.slice(0, index + 1);
|
||||
let newActiveTabId = activeTabId;
|
||||
if (activeTabId && !newTabs.some((tab) => tab.id === activeTabId)) {
|
||||
newActiveTabId = tabId;
|
||||
}
|
||||
|
||||
set({
|
||||
tabs: newTabs,
|
||||
activeTabId: newActiveTabId,
|
||||
});
|
||||
if (newActiveTabId) {
|
||||
const nextActive = get().tabs.find((t) => t.id === newActiveTabId);
|
||||
if (nextActive) ensureTabPageMenuAuthority(get, set, nextActive);
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: TAB_STORE_PERSIST_KEY,
|
||||
|
||||
Reference in New Issue
Block a user