This commit is contained in:
@@ -4,4 +4,5 @@ NEXT_PUBLIC_PROXY_HOST=http://localhost:3001
|
||||
SERVER_API_URL=http://192.168.0.102:8000
|
||||
NEXT_PUBLIC_SITE_ID=ST
|
||||
NEXT_PUBLIC_SUB_DOMAIN=http://localhost:3000
|
||||
NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2
|
||||
NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2
|
||||
NEXT_PUBLIC_ST_DASHBOARD_BBS_ID=13
|
||||
@@ -6,7 +6,7 @@
|
||||
}
|
||||
/* 셀 클래스가 있으면 위 기본색보다 우선 (같은 specificity + 나중 규칙) */
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly {
|
||||
@apply !bg-[#f9f8f8] !text-[#666];
|
||||
@apply !bg-[#f0f0f0] !text-[#666];
|
||||
}
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-verify-error {
|
||||
@apply !bg-[#ffe6e6] !text-[#c00];
|
||||
|
||||
@@ -457,10 +457,29 @@ function sortRowsGroupPreserving(
|
||||
}
|
||||
|
||||
/** columnInfo → RealGrid DataField 배열 (computed → valueCallback 포함, setFields용) */
|
||||
function getDecimalPlacesFromDataFormat(dataFormat?: string): number | null {
|
||||
if (!dataFormat) return null;
|
||||
const match = dataFormat.match(/\.(0+|#+)/);
|
||||
const decimalToken = match?.[1];
|
||||
if (!decimalToken) return null;
|
||||
return decimalToken.length;
|
||||
}
|
||||
|
||||
function truncateByDecimalPlaces(value: number, decimalPlaces: number): number {
|
||||
if (!Number.isFinite(value)) return 0;
|
||||
if (decimalPlaces < 0) return value;
|
||||
const factor = 10 ** decimalPlaces;
|
||||
const scaled = value * factor;
|
||||
// 절삭(truncate): 반올림 없이 소수점 이하를 버린다.
|
||||
const truncated = scaled >= 0 ? Math.floor(scaled) : Math.ceil(scaled);
|
||||
return truncated / factor;
|
||||
}
|
||||
|
||||
function buildFieldsFromColumns(
|
||||
columns: {
|
||||
fieldName: string;
|
||||
valueType?: ValueType;
|
||||
dataFormat?: string;
|
||||
datetimeFormat?: string;
|
||||
editor?: { type?: string; datetimeFormat?: string };
|
||||
computed?: import("../type/DataGrid.type").ComputedFieldConfig;
|
||||
@@ -509,17 +528,23 @@ function buildFieldsFromColumns(
|
||||
"subtrahendFields" in col.computed &&
|
||||
col.computed.minuendFields.length > 0;
|
||||
const hasSumOfMaxGroups =
|
||||
col.computed.type === "sumOfMaxOfFieldAndSumGroups" &&
|
||||
"groups" in col.computed &&
|
||||
Array.isArray(col.computed.groups) &&
|
||||
col.computed.groups.length > 0;
|
||||
const hasPumpGrossPpqty =
|
||||
col.computed.type === "pumpGrossPpqty" &&
|
||||
"pumpGroups" in col.computed &&
|
||||
col.computed.pumpGroups.length > 0;
|
||||
if (
|
||||
hasFields ||
|
||||
hasNumeratorDenominator ||
|
||||
hasMaxOfFieldAndSum ||
|
||||
hasDifferenceOfSums ||
|
||||
hasSumOfMaxGroups
|
||||
hasSumOfMaxGroups ||
|
||||
hasPumpGrossPpqty
|
||||
) {
|
||||
field.valueCallback = getComputedValueCallback(
|
||||
const computedCallback = getComputedValueCallback(
|
||||
col.computed,
|
||||
) as (
|
||||
prod: unknown,
|
||||
@@ -528,6 +553,24 @@ function buildFieldsFromColumns(
|
||||
fieldNames: string[],
|
||||
values: unknown[],
|
||||
) => number;
|
||||
const decimalPlaces = getDecimalPlacesFromDataFormat(col.dataFormat);
|
||||
field.valueCallback = (
|
||||
prod: unknown,
|
||||
dataRow: number,
|
||||
fieldName: string,
|
||||
fieldNames: string[],
|
||||
values: unknown[],
|
||||
) => {
|
||||
const computedValue = computedCallback(
|
||||
prod,
|
||||
dataRow,
|
||||
fieldName,
|
||||
fieldNames,
|
||||
values,
|
||||
);
|
||||
if (decimalPlaces == null) return computedValue;
|
||||
return truncateByDecimalPlaces(computedValue, decimalPlaces);
|
||||
};
|
||||
}
|
||||
}
|
||||
return field;
|
||||
@@ -1046,6 +1089,14 @@ const DataGrid = ({
|
||||
}
|
||||
columns.forEach((column) => {
|
||||
const gv = gridView as GridViewWithSetColumnProperty;
|
||||
const exists =
|
||||
typeof (
|
||||
gridView as { columnByName?: (name: string) => unknown }
|
||||
).columnByName === "function" &&
|
||||
(gridView as { columnByName: (name: string) => unknown }).columnByName(
|
||||
column.name,
|
||||
) != null;
|
||||
if (!exists) return;
|
||||
gv.setColumnProperty?.(
|
||||
column.name,
|
||||
"header",
|
||||
@@ -1181,10 +1232,63 @@ const DataGrid = ({
|
||||
const hasFooterUserSpans = checkFooterUserSpans(columnLayout);
|
||||
|
||||
if (hasFooterUserSpans) {
|
||||
const validColumnNames = new Set(columns.map((c) => c.name));
|
||||
const sanitizeColumnLayout = (
|
||||
layout: unknown[],
|
||||
): unknown[] => {
|
||||
const result: unknown[] = [];
|
||||
for (const item of layout) {
|
||||
if (typeof item === "string") {
|
||||
if (validColumnNames.has(item)) {
|
||||
result.push(item);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!item || typeof item !== "object") continue;
|
||||
|
||||
if ("column" in item) {
|
||||
const columnName = (item as { column?: unknown }).column;
|
||||
if (
|
||||
typeof columnName === "string" &&
|
||||
validColumnNames.has(columnName)
|
||||
) {
|
||||
result.push(item);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ("items" in item && Array.isArray((item as { items?: unknown[] }).items)) {
|
||||
const nextItems = sanitizeColumnLayout(
|
||||
(item as { items: unknown[] }).items,
|
||||
);
|
||||
if (nextItems.length > 0) {
|
||||
result.push({
|
||||
...(item as Record<string, unknown>),
|
||||
items: nextItems,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push(item);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// setColumnLayout은 그리드가 완전히 렌더링된 후 호출해야 함
|
||||
setTimeout(() => {
|
||||
if (gridRef.current?.gridView && columnLayout) {
|
||||
gridRef.current.gridView.setColumnLayout?.(columnLayout);
|
||||
const safeLayout = sanitizeColumnLayout(columnLayout as unknown[]);
|
||||
if (safeLayout.length === 0) return;
|
||||
try {
|
||||
(
|
||||
gridRef.current.gridView.setColumnLayout as
|
||||
| ((layout: unknown) => void)
|
||||
| undefined
|
||||
)?.(safeLayout);
|
||||
} catch (e) {
|
||||
console.warn("setColumnLayout skipped due to invalid columns:", e);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import DataGrid from "@/components/module/DataGrid";
|
||||
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
||||
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
|
||||
/** 통계 입력 현황 > 행 클릭 시 예외처리(prcsYn=3) 내역만 조회 */
|
||||
/** 통계 입력 현황 > 행 클릭 시 사용자예외처리(prcsYn=2) 내역만 조회 */
|
||||
export interface ExceptionProcessingHistoryQueryContext {
|
||||
sewerBzmnCd: string;
|
||||
ctpvNm: string;
|
||||
@@ -92,13 +92,15 @@ function exceptionPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams)
|
||||
const exceptionHistoryColumnInfo: ColumnInfo = {
|
||||
columns: [
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
||||
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
|
||||
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false },
|
||||
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
||||
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
|
||||
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "pbcpAprvYn", fieldName: "pbcpAprvYn", header: { text: "공단승인여부" }, editable: false },
|
||||
{ name: "stVrfRjtComNm", fieldName: "stVrfRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false },
|
||||
{ name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
|
||||
{
|
||||
@@ -114,13 +116,15 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
|
||||
],
|
||||
columnLayout: [
|
||||
"ctpvNm",
|
||||
"sggNm",
|
||||
"admdstNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYnNm",
|
||||
"pbcpAprvYn",
|
||||
"stVrfRjtComNm",
|
||||
"prcsOpnnExpln",
|
||||
"rjctOpnnCn",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"individualReject",
|
||||
@@ -190,9 +194,9 @@ export function ExceptionProcessingHistoryDialog({
|
||||
sggNm: queryContext.sggNm,
|
||||
statsYr: queryContext.statsYr,
|
||||
statsCd: queryContext.statsCd,
|
||||
pbcpAprvYn: "N",
|
||||
vrfcMthdCd,
|
||||
prcsYnList: PRCS_YN_LIST,
|
||||
rejectCodeList: "0,1,2",
|
||||
page: pageIndex + 1,
|
||||
size: 30,
|
||||
};
|
||||
@@ -308,14 +312,16 @@ export function ExceptionProcessingHistoryDialog({
|
||||
const mapRows = (entries: VerificationResultReportItem[]): ExceptionHistoryGridRow[] =>
|
||||
entries.map((item) => ({
|
||||
ctpvNm: item.ctpvNm,
|
||||
sggNm: item.sggNm,
|
||||
admdstNm: item.admdstNm ?? item.sggNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYnNm: item.prcsYnNm ?? "-",
|
||||
stVrfRjtComNm: item.stVrfRjtComNm ?? "-",
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "-",
|
||||
rjctOpnnCn: item.rjctOpnnCn ?? "-",
|
||||
prcsYn: item.prcsYn ?? "",
|
||||
pbcpAprvYn: item.pbcpAprvYn,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
individualReject: "개별 반려",
|
||||
|
||||
@@ -41,7 +41,7 @@ const getSggListFetcher = async (
|
||||
key: [string, string, string, string],
|
||||
): Promise<ApiResponse<Sgg[]>> => {
|
||||
const gugunSkipYn = doList.includes(key[3]) ? "N" : "Y";
|
||||
return statisticsApi.getSggAllList(key[1], {
|
||||
return statisticsApi.getSggList(key[1], {
|
||||
instSeCd: key[2],
|
||||
gugunSkipYn,
|
||||
});
|
||||
|
||||
@@ -92,13 +92,12 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
{ name: "vrfcMthdNm", fieldName: "vrfcMthdNm", header: { text: "구분" }, editable: false },
|
||||
{ name: "vrfcMthdCd", fieldName: "vrfcMthdCd", header: { text: "검증유형코드" }, editable: false, visible: false },
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
||||
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
|
||||
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false },
|
||||
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
||||
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
|
||||
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "pbcpAprvYn", fieldName: "pbcpAprvYn", header: { text: "공단승인여부" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
|
||||
{
|
||||
@@ -111,13 +110,12 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
columnLayout: [
|
||||
"vrfcMthdNm",
|
||||
"ctpvNm",
|
||||
"sggNm",
|
||||
"admdstNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYn",
|
||||
"pbcpAprvYn",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"prcsOpnnExpln",
|
||||
@@ -202,13 +200,12 @@ export function VerificationResultDialog({
|
||||
vrfcMthdCd,
|
||||
vrfcMthdNm: VRFC_MTHD_NAME_MAP[vrfcMthdCd] ?? item.vrfcMthdNm,
|
||||
ctpvNm: item.ctpvNm,
|
||||
sggNm: item.sggNm,
|
||||
admdstNm: item.admdstNm ?? item.sggNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYn: PRCS_YN_LABEL[item.prcsYn] ?? item.prcsYn,
|
||||
pbcpAprvYn: item.pbcpAprvYn,
|
||||
prcsYn: item.prcsYnNm ?? PRCS_YN_LABEL[item.prcsYn] ?? item.prcsYn,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "",
|
||||
@@ -226,9 +223,9 @@ export function VerificationResultDialog({
|
||||
sggNm: stableQueryContext.sggNm,
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
pbcpAprvYn: "N",
|
||||
vrfcMthdCd,
|
||||
prcsYnList: "0",
|
||||
rejectCodeList: "0,1,2",
|
||||
page: page + 1,
|
||||
size: 10000,
|
||||
};
|
||||
|
||||
@@ -137,12 +137,15 @@ function verificationPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackPara
|
||||
const verificationResultColumnInfo: ColumnInfo = {
|
||||
columns: [
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
||||
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
|
||||
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false },
|
||||
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
||||
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
|
||||
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "stVrfRjtComNm", fieldName: "stVrfRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false },
|
||||
{ name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
|
||||
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
@@ -150,12 +153,15 @@ const verificationResultColumnInfo: ColumnInfo = {
|
||||
],
|
||||
columnLayout: [
|
||||
"ctpvNm",
|
||||
"sggNm",
|
||||
"admdstNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYnNm",
|
||||
"stVrfRjtComNm",
|
||||
"prcsOpnnExpln",
|
||||
"rjctOpnnCn",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"prcsYn",
|
||||
@@ -245,9 +251,9 @@ export function VerificationResultHistoryDialog({
|
||||
sggNm: stableQueryContext.sggNm,
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
pbcpAprvYn: "N",
|
||||
vrfcMthdCd,
|
||||
prcsYnList: "0,2,3",
|
||||
prcsYnList: "0,2",
|
||||
rejectCodeList: "0,1,2",
|
||||
page: pageIndex + 1,
|
||||
size: PAGE_SIZE,
|
||||
};
|
||||
@@ -376,14 +382,16 @@ export function VerificationResultHistoryDialog({
|
||||
vrfcSn: item.vrfcSn,
|
||||
__checked: isChecked ? 1 : 0,
|
||||
ctpvNm: item.ctpvNm,
|
||||
sggNm: item.sggNm,
|
||||
admdstNm: item.admdstNm ?? item.sggNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYnNm: item.prcsYnNm ?? "-",
|
||||
stVrfRjtComNm: item.stVrfRjtComNm ?? "-",
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "-",
|
||||
rjctOpnnCn: item.rjctOpnnCn ?? "-",
|
||||
prcsYn: item.prcsYn ?? "",
|
||||
pbcpAprvYn: item.pbcpAprvYn,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
};
|
||||
|
||||
@@ -34,8 +34,8 @@ export async function hasAnyPostSubmitVerificationResult(
|
||||
sggNm: ctx.sggNm,
|
||||
statsYr: ctx.statsYr,
|
||||
statsCd: ctx.statsCd,
|
||||
pbcpAprvYn: "N" as const,
|
||||
prcsYnList: "0",
|
||||
rejectCodeList: "0,1,2",
|
||||
page: 1,
|
||||
size: 1,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
type InfiniteData,
|
||||
keepPreviousData,
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
} from "@tanstack/react-query";
|
||||
import { useTabStore as useGridTabStore } from "@rsone/components";
|
||||
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { verificationInputReportApi } from "@/entities/statistics/api/verification-input-report-service";
|
||||
import type {
|
||||
VerificationResultListRequest,
|
||||
VerificationResultListResponse,
|
||||
VerificationResultReportItem,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
VerificationInputReportItem,
|
||||
VerificationInputReportResponse,
|
||||
VerificationInputReportTypeCountingInfo,
|
||||
} from "@/entities/statistics/model/verification-input-report-interface";
|
||||
import { useAlertStore, useMenuStore } from "@/shared/lib";
|
||||
import {
|
||||
findMenuItemByUrlPath,
|
||||
@@ -59,8 +59,8 @@ const TYPE_CODE_NAME_MAP: Record<string, string> = {
|
||||
const PAGE_SIZE = 100;
|
||||
const SCROLL_LOAD_THRESHOLD_PX = 120;
|
||||
|
||||
function getNextVerificationResultPage(
|
||||
lastPage: VerificationResultListResponse,
|
||||
function getNextVerificationInputReportPage(
|
||||
lastPage: VerificationInputReportResponse,
|
||||
): number | undefined {
|
||||
const pageInfo = lastPage?.data?.pageInfo;
|
||||
if (!pageInfo) return undefined;
|
||||
@@ -69,19 +69,29 @@ function getNextVerificationResultPage(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function flattenVerificationResults(
|
||||
data: InfiniteData<VerificationResultListResponse> | undefined,
|
||||
): VerificationResultReportItem[] {
|
||||
function flattenVerificationInputReportResults(
|
||||
data: InfiniteData<VerificationInputReportResponse> | undefined,
|
||||
): VerificationInputReportItem[] {
|
||||
return (
|
||||
data?.pages.flatMap((page) => page?.data?.verifyResultList ?? []) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
function getVerificationResultTotalCount(
|
||||
data: InfiniteData<VerificationResultListResponse> | undefined,
|
||||
loadedCount: number,
|
||||
): number {
|
||||
return data?.pages[0]?.data?.pageInfo?.totalElements ?? loadedCount;
|
||||
function getVerifyTypeCountingInfo(
|
||||
data: InfiniteData<VerificationInputReportResponse> | undefined,
|
||||
): VerificationInputReportTypeCountingInfo | null {
|
||||
return data?.pages[0]?.data?.verifyTypeCountingInfo ?? null;
|
||||
}
|
||||
|
||||
function formatReportItemTitle(item: VerificationInputReportItem): string {
|
||||
return item.statsNm?.trim() || item.vrfcTrgtNm?.trim() || item.statsCd;
|
||||
}
|
||||
|
||||
function formatReportItemRegion(item: VerificationInputReportItem): string {
|
||||
const parts = [item.ctpvNm, item.sggNm].filter(
|
||||
(v): v is string => Boolean(v?.trim()),
|
||||
);
|
||||
return parts.length > 0 ? parts.join(" ") : "-";
|
||||
}
|
||||
|
||||
function useVerificationReportList(
|
||||
@@ -89,28 +99,31 @@ function useVerificationReportList(
|
||||
statsCdQueryKey: string,
|
||||
vrfcMthdCd: VerificationReportTabKey,
|
||||
statsCd: string,
|
||||
enabled: boolean,
|
||||
) {
|
||||
return useInfiniteQuery({
|
||||
queryKey: [
|
||||
"verificationResultList",
|
||||
"verificationInputReport",
|
||||
statsYr,
|
||||
vrfcMthdCd,
|
||||
statsCdQueryKey,
|
||||
],
|
||||
initialPageParam: 1,
|
||||
queryFn: ({ pageParam }) =>
|
||||
statisticsApi.getVerificationResultList({
|
||||
verificationInputReportApi.getVerificationInputReport({
|
||||
statsYr,
|
||||
pbcpAprvYn: "N",
|
||||
vrfcMthdCd,
|
||||
page: pageParam,
|
||||
size: PAGE_SIZE,
|
||||
prcsYnList: "0",
|
||||
vrfcMthdCd,
|
||||
...(!(statsCd === "" || statsCd === "ALL") && { statsCd }),
|
||||
} satisfies VerificationResultListRequest),
|
||||
getNextPageParam: getNextVerificationResultPage,
|
||||
}),
|
||||
getNextPageParam: getNextVerificationInputReportPage,
|
||||
placeholderData: keepPreviousData,
|
||||
refetchOnWindowFocus: false,
|
||||
// 탭 전환만으로는 재조회하지 않음(명시적 새로고침/조건 변경 시만 조회)
|
||||
staleTime: Infinity,
|
||||
refetchOnMount: false,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -132,28 +145,46 @@ const VerificationSelectionReport = ({
|
||||
const alert = useAlertStore((s) => s.setMessage);
|
||||
const [activeTab, setActiveTab] = useState<VerificationReportTabKey>("NV");
|
||||
const [statsCd, setStatsCd] = useState<string>("ALL");
|
||||
const [isFullReloading, setIsFullReloading] = useState(false);
|
||||
|
||||
const statsYr = String(dayjs().year() - 1);
|
||||
const statsCdQueryKey =
|
||||
statsCd === "" || statsCd === "ALL" ? "" : statsCd;
|
||||
|
||||
const handleStatsCdChange = useCallback(
|
||||
(nextStatsCd: string) => {
|
||||
setStatsCd(nextStatsCd);
|
||||
setActiveTab("NV");
|
||||
setIsFullReloading(true);
|
||||
|
||||
// 통계항목 변경 시 모든 탭 캐시 제거 후 NV부터 다시 조회
|
||||
void queryClient.removeQueries({
|
||||
queryKey: ["verificationInputReport", statsYr],
|
||||
});
|
||||
},
|
||||
[queryClient, statsYr],
|
||||
);
|
||||
|
||||
const nvQuery = useVerificationReportList(
|
||||
statsYr,
|
||||
statsCdQueryKey,
|
||||
"NV",
|
||||
statsCd,
|
||||
activeTab === "NV",
|
||||
);
|
||||
const imrQuery = useVerificationReportList(
|
||||
statsYr,
|
||||
statsCdQueryKey,
|
||||
"IMR",
|
||||
statsCd,
|
||||
activeTab === "IMR",
|
||||
);
|
||||
const hbQuery = useVerificationReportList(
|
||||
statsYr,
|
||||
statsCdQueryKey,
|
||||
"HB",
|
||||
statsCd,
|
||||
activeTab === "HB",
|
||||
);
|
||||
|
||||
const queryByTab = {
|
||||
@@ -162,44 +193,51 @@ const VerificationSelectionReport = ({
|
||||
HB: hbQuery,
|
||||
} as const;
|
||||
|
||||
const isInitialLoading =
|
||||
nvQuery.isPending || imrQuery.isPending || hbQuery.isPending;
|
||||
const isRefreshing =
|
||||
(nvQuery.isFetching && !nvQuery.isPending) ||
|
||||
(imrQuery.isFetching && !imrQuery.isPending) ||
|
||||
(hbQuery.isFetching && !hbQuery.isPending);
|
||||
const error = nvQuery.error ?? imrQuery.error ?? hbQuery.error;
|
||||
const activeQuery = queryByTab[activeTab];
|
||||
const isRefreshing = activeQuery.isFetching && !activeQuery.isPending;
|
||||
const error = activeQuery.error;
|
||||
|
||||
/** 통계항목 변경·최초 진입: 탭 영역 숨기고 카드 전체 로딩만 표시 */
|
||||
const hideTabsSection =
|
||||
isFullReloading ||
|
||||
(activeTab === "NV" && !nvQuery.data && (nvQuery.isPending || nvQuery.isFetching));
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFullReloading) return;
|
||||
if (nvQuery.isFetching) return;
|
||||
if (nvQuery.isSuccess || nvQuery.isError) {
|
||||
setIsFullReloading(false);
|
||||
}
|
||||
}, [isFullReloading, nvQuery.isFetching, nvQuery.isSuccess, nvQuery.isError]);
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
const tabCodes: VerificationReportTabKey[] = ["NV", "IMR", "HB"];
|
||||
void Promise.all(
|
||||
tabCodes.map((code) =>
|
||||
queryClient.resetQueries({
|
||||
queryKey: [
|
||||
"verificationResultList",
|
||||
statsYr,
|
||||
code,
|
||||
statsCdQueryKey,
|
||||
],
|
||||
}),
|
||||
),
|
||||
);
|
||||
}, [queryClient, statsYr, statsCdQueryKey]);
|
||||
setStatsCd("ALL");
|
||||
setActiveTab("NV");
|
||||
setIsFullReloading(true);
|
||||
void queryClient.removeQueries({
|
||||
queryKey: ["verificationInputReport", statsYr],
|
||||
});
|
||||
}, [queryClient, statsYr]);
|
||||
|
||||
const listsByTab: Record<VerificationReportTabKey, VerificationResultReportItem[]> = {
|
||||
NV: flattenVerificationResults(nvQuery.data),
|
||||
IMR: flattenVerificationResults(imrQuery.data),
|
||||
HB: flattenVerificationResults(hbQuery.data),
|
||||
const listsByTab: Record<VerificationReportTabKey, VerificationInputReportItem[]> = {
|
||||
NV: flattenVerificationInputReportResults(nvQuery.data),
|
||||
IMR: flattenVerificationInputReportResults(imrQuery.data),
|
||||
HB: flattenVerificationInputReportResults(hbQuery.data),
|
||||
};
|
||||
|
||||
const typeCountingInfo =
|
||||
getVerifyTypeCountingInfo(nvQuery.data) ??
|
||||
getVerifyTypeCountingInfo(imrQuery.data) ??
|
||||
getVerifyTypeCountingInfo(hbQuery.data);
|
||||
|
||||
const totalCountByTab: Record<VerificationReportTabKey, number> = {
|
||||
NV: getVerificationResultTotalCount(nvQuery.data, listsByTab.NV.length),
|
||||
IMR: getVerificationResultTotalCount(imrQuery.data, listsByTab.IMR.length),
|
||||
HB: getVerificationResultTotalCount(hbQuery.data, listsByTab.HB.length),
|
||||
NV: typeCountingInfo?.nvCount ?? listsByTab.NV.length,
|
||||
IMR: typeCountingInfo?.imrCount ?? listsByTab.IMR.length,
|
||||
HB: typeCountingInfo?.hbCount ?? listsByTab.HB.length,
|
||||
};
|
||||
|
||||
const openInputFromStatsCd = useCallback(
|
||||
(item: VerificationResultReportItem) => {
|
||||
(item: VerificationInputReportItem) => {
|
||||
const nav = resolveInputPageNavigationFromStatsCd(item.statsCd);
|
||||
if (!nav) {
|
||||
alert(
|
||||
@@ -263,9 +301,21 @@ const VerificationSelectionReport = ({
|
||||
[],
|
||||
);
|
||||
|
||||
const isTabContentLoading = (tab: VerificationReportTabKey) => {
|
||||
const query = queryByTab[tab];
|
||||
return (
|
||||
activeTab === tab &&
|
||||
!hideTabsSection &&
|
||||
(query.isPending ||
|
||||
(query.isFetching && !query.isFetchingNextPage) ||
|
||||
(!query.data && !query.error))
|
||||
);
|
||||
};
|
||||
|
||||
const renderResultList = (
|
||||
typeCode: VerificationReportTabKey,
|
||||
items: VerificationResultReportItem[],
|
||||
items: VerificationInputReportItem[],
|
||||
isTabLoading: boolean,
|
||||
hasNextPage: boolean,
|
||||
isFetchingNextPage: boolean,
|
||||
fetchNextPage: () => void,
|
||||
@@ -281,7 +331,13 @@ const VerificationSelectionReport = ({
|
||||
)
|
||||
}
|
||||
>
|
||||
{items.length === 0 && !isFetchingNextPage ? (
|
||||
{isTabLoading ? (
|
||||
<div className="flex h-full min-h-[8rem] items-center justify-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
) : items.length === 0 && !isFetchingNextPage ? (
|
||||
<div className="flex h-full min-h-[8rem] items-center justify-center text-lg text-muted-foreground">
|
||||
항목이 없습니다.
|
||||
</div>
|
||||
@@ -303,7 +359,7 @@ const VerificationSelectionReport = ({
|
||||
>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<span className="font-medium">
|
||||
{item.statsNm}
|
||||
{formatReportItemTitle(item)}
|
||||
</span>
|
||||
<Badge
|
||||
color={
|
||||
@@ -320,7 +376,7 @@ const VerificationSelectionReport = ({
|
||||
{item.rsnExpln}
|
||||
</p>
|
||||
<div className="flex justify-between text-lg text-muted-foreground">
|
||||
<span>{item.admdstNm}</span>
|
||||
<span>{formatReportItemRegion(item)}</span>
|
||||
<span>
|
||||
현재: {item.nowVlCn ?? "-"} / 비교:{" "}
|
||||
{item.compVlCn ?? "-"}
|
||||
@@ -383,25 +439,25 @@ const VerificationSelectionReport = ({
|
||||
<CodeSelectWithAll
|
||||
upCd="STITEM"
|
||||
value={statsCd}
|
||||
onValueChange={setStatsCd}
|
||||
onValueChange={handleStatsCdChange}
|
||||
placeholder="전체"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="card-body p-2.5 flex-1 flex flex-col overflow-hidden relative min-h-0">
|
||||
{isInitialLoading && (
|
||||
{hideTabsSection && (
|
||||
<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>
|
||||
)}
|
||||
{error && (
|
||||
{!hideTabsSection && error && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
데이터를 불러올 수 없습니다.
|
||||
</div>
|
||||
)}
|
||||
{!isInitialLoading && !error && (
|
||||
{!hideTabsSection && !error && (
|
||||
<section className="flex min-h-0 flex-1 flex-col">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
@@ -428,18 +484,14 @@ const VerificationSelectionReport = ({
|
||||
value={tab.value}
|
||||
className="mt-0 flex min-h-0 flex-1 flex-col p-0 data-[state=inactive]:hidden"
|
||||
>
|
||||
{activeTab === tab.value
|
||||
? renderResultList(
|
||||
tab.value,
|
||||
listsByTab[tab.value],
|
||||
queryByTab[tab.value]
|
||||
.hasNextPage ?? false,
|
||||
queryByTab[tab.value]
|
||||
.isFetchingNextPage,
|
||||
queryByTab[tab.value]
|
||||
.fetchNextPage,
|
||||
)
|
||||
: null}
|
||||
{renderResultList(
|
||||
tab.value,
|
||||
listsByTab[tab.value],
|
||||
isTabContentLoading(tab.value),
|
||||
queryByTab[tab.value].hasNextPage ?? false,
|
||||
queryByTab[tab.value].isFetchingNextPage,
|
||||
queryByTab[tab.value].fetchNextPage,
|
||||
)}
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState, type Dispatch, type SetStateAction } from 'react';
|
||||
|
||||
import { useConfirm } from '@frontend-opensource/use-react-hooks';
|
||||
|
||||
@@ -157,7 +157,9 @@ const PublicSewageFacilityInp = () => {
|
||||
const globalProgressVisible = useGlobalProgressStore((state) => state.visible);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [hasSearched, setHasSearched] = useState(false);
|
||||
const [isNotApplicable, setIsNotApplicable] = useState(false);
|
||||
const [isNotApplicableByTab, setIsNotApplicableByTab] = useState<Map<string, boolean>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [showIsNotApplicable, setShowIsNotApplicable] = useState(false);
|
||||
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
|
||||
@@ -175,6 +177,19 @@ const PublicSewageFacilityInp = () => {
|
||||
const [mainFacilityMappingSaveDialogKey, setMainFacilityMappingSaveDialogKey] = useState(0);
|
||||
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const isNotApplicable = isNotApplicableByTab.get('ST00060001') ?? false;
|
||||
const setIsNotApplicable: Dispatch<SetStateAction<boolean>> = (next) => {
|
||||
setIsNotApplicableByTab((prev) => {
|
||||
const current = prev.get('ST00060001') ?? false;
|
||||
const resolved =
|
||||
typeof next === 'function'
|
||||
? (next as (prevState: boolean) => boolean)(current)
|
||||
: next;
|
||||
const nextMap = new Map(prev);
|
||||
nextMap.set('ST00060001', resolved);
|
||||
return nextMap;
|
||||
});
|
||||
};
|
||||
|
||||
const periodEnterableRef = useRef(true);
|
||||
const searchDoneRef = useRef(false);
|
||||
@@ -655,8 +670,6 @@ const PublicSewageFacilityInp = () => {
|
||||
param: PublicSewageFacilityRequestParams,
|
||||
searchContext: SearchFormValues | null,
|
||||
): Promise<boolean> => {
|
||||
// 서버 데이터로 새로 불러오는 시점이므로 "해당없음" 체크는 항상 초기화
|
||||
setIsNotApplicable(false);
|
||||
try {
|
||||
if (tabKey === 'ST00060001') {
|
||||
const response = await statisticsInputPublicSewageFacilityApiService.getPublicSewageFacility(param);
|
||||
@@ -856,6 +869,11 @@ const PublicSewageFacilityInp = () => {
|
||||
setLastSearchedValues(values);
|
||||
searchDoneRef.current = true;
|
||||
setHasSearched(true);
|
||||
setIsNotApplicableByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('ST00060001', false);
|
||||
return next;
|
||||
});
|
||||
|
||||
setIsLoading(true);
|
||||
const param = buildPublicSewageFacilityParams(values);
|
||||
@@ -870,6 +888,19 @@ const PublicSewageFacilityInp = () => {
|
||||
};
|
||||
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
const prevTabKey = activeTabRef.current;
|
||||
if (prevTabKey === 'ST00060001') {
|
||||
const latestRowsInPrevTab =
|
||||
st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? [];
|
||||
if (latestRowsInPrevTab.length > 0 || rowDataMap.has('ST00060001')) {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('ST00060001', latestRowsInPrevTab);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
activeTabRef.current = tabKey;
|
||||
setActiveTab(tabKey);
|
||||
|
||||
@@ -885,7 +916,7 @@ const PublicSewageFacilityInp = () => {
|
||||
const handleReset = () => {
|
||||
setIsLoading(false);
|
||||
setHasSearched(false);
|
||||
setIsNotApplicable(false);
|
||||
setIsNotApplicableByTab(new Map());
|
||||
setShowIsNotApplicable(false);
|
||||
|
||||
setRowDataMap(new Map());
|
||||
@@ -1215,7 +1246,7 @@ const PublicSewageFacilityInp = () => {
|
||||
return (
|
||||
<TabsContent key={tab.tabKey} value={tab.tabKey}>
|
||||
<DataGrid
|
||||
key={(tabEnterableForGrid || isAdminInfo) ? 'enterable' : 'readonly'}
|
||||
key={tab.tabKey}
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={tab.rowStartNum}
|
||||
excelExportFileSuffix={tab.tabLabel || tab.tabKey}
|
||||
|
||||
@@ -5,7 +5,6 @@ import DataGrid from "@/components/module/DataGrid";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
import AddArticleButton from "@/components/button/AddArticleButton";
|
||||
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
|
||||
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
|
||||
@@ -15,11 +14,6 @@ import { PumpingStationInpColumn } from "@/components/util/column/input/PumpingS
|
||||
// 하수도 통계 관리 > 05. 펌프장
|
||||
const PumpingStationInp = () => {
|
||||
|
||||
const SubmitExceptionFunc = () => {
|
||||
console.log('submit exception');
|
||||
// 예외 제출 로직
|
||||
};
|
||||
|
||||
const getLastYearDataFunc = () => {
|
||||
console.log('get last year data');
|
||||
// 지난연도 데이터 가져오기 로직
|
||||
@@ -53,7 +47,6 @@ const PumpingStationInp = () => {
|
||||
<SearchForm fctName={true} />
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
|
||||
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
<SaveButton saveFunc={saveFunc} />
|
||||
@@ -67,7 +60,7 @@ const PumpingStationInp = () => {
|
||||
</div>
|
||||
</div>
|
||||
<DataGrid
|
||||
columnInfo={ PumpingStationInpColumn }
|
||||
columnInfo={PumpingStationInpColumn}
|
||||
rowStartNum={3}
|
||||
fixedColumns={3}
|
||||
rowData={rowData}
|
||||
|
||||
@@ -396,6 +396,8 @@ const SewagePipeInp = () => {
|
||||
values: SearchFormValues;
|
||||
tabKey: string;
|
||||
} | null>(null);
|
||||
/** ST00020002 무데이터 안내/자동이동은 동일 조회조건에서 1회만 수행 */
|
||||
const factInstallNoDataHandledKeyRef = useRef<string | null>(null);
|
||||
|
||||
const [verificationSearchDefaults, setVerificationSearchDefaults] =
|
||||
useState<SearchFormValues | null>(null);
|
||||
@@ -549,6 +551,12 @@ const SewagePipeInp = () => {
|
||||
mergeIntoExistingMap: boolean,
|
||||
updateLastSearchedValues: boolean,
|
||||
): Promise<boolean> => {
|
||||
const searchConditionKey = JSON.stringify({
|
||||
statsYr: searchParam.statsYr ?? "",
|
||||
ctpvNm: searchParam.ctpvNm ?? "",
|
||||
sggNm: searchParam.sggNm ?? "",
|
||||
sewerBzmnCd: searchParam.sewerBzmnCd ?? "",
|
||||
});
|
||||
const applyRowMap = (apply: (m: Map<string, Record<string, string | number>[]>) => void) => {
|
||||
if (mergeIntoExistingMap) {
|
||||
setRowDataMap((prev) => {
|
||||
@@ -595,6 +603,23 @@ const SewagePipeInp = () => {
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
...d,
|
||||
}));
|
||||
if (convertedData.length === 0) {
|
||||
const alreadyHandledForSameSearch =
|
||||
factInstallNoDataHandledKeyRef.current === searchConditionKey;
|
||||
if (!alreadyHandledForSameSearch) {
|
||||
factInstallNoDataHandledKeyRef.current = searchConditionKey;
|
||||
alert("시설별 설치 현황에 조회된 데이터가 없습니다.\n시설별 현황 관리에서 설정해주세요");
|
||||
activeTabRef.current = "ST00020006";
|
||||
setActiveTab("ST00020006");
|
||||
return await loadSewagePipeActiveTabData(
|
||||
"ST00020006",
|
||||
searchParam,
|
||||
values,
|
||||
true,
|
||||
updateLastSearchedValues,
|
||||
);
|
||||
}
|
||||
}
|
||||
applyRowMap((m) => m.set('ST00020002', toGridRows(convertedData)));
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
@@ -795,7 +820,39 @@ const SewagePipeInp = () => {
|
||||
|
||||
}, [searchFormRemountKey]);
|
||||
|
||||
const getCurrentRowsForTab = (tabKey: string): Record<string, string | number>[] => {
|
||||
switch (tabKey) {
|
||||
case REGIONAL_FACT_TAB_KEY:
|
||||
return getGridDataRef.current?.() ?? [];
|
||||
case "ST00020002":
|
||||
return getFactInstallGridDataRef.current?.() ?? [];
|
||||
case "ST00020003":
|
||||
return getPipeTypeGridDataRef.current?.() ?? [];
|
||||
case "ST00020004":
|
||||
return getPipeDiameterGridDataRef.current?.() ?? [];
|
||||
case "ST00020005":
|
||||
return getManholeGridDataRef.current?.() ?? [];
|
||||
case "ST00020006":
|
||||
return [
|
||||
...(rowDataMap.get("treatmentPlantConnectionInp") ?? []),
|
||||
...(rowDataMap.get("adminAreaConnectionInp") ?? []),
|
||||
];
|
||||
default:
|
||||
return rowDataMap.get(tabKey) ?? [];
|
||||
}
|
||||
};
|
||||
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
const prevTabKey = activeTabRef.current;
|
||||
const latestRowsInPrevTab = getCurrentRowsForTab(prevTabKey);
|
||||
if (latestRowsInPrevTab.length > 0 || rowDataMap.has(prevTabKey)) {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(prevTabKey, latestRowsInPrevTab);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
activeTabRef.current = tabKey;
|
||||
setActiveTab(tabKey);
|
||||
setStatsCd(tabStatsCdMap.get(tabKey) ?? "");
|
||||
@@ -1735,7 +1792,7 @@ const SewagePipeInp = () => {
|
||||
const maxYy = getManholeLastDataYear() % 100;
|
||||
|
||||
const saveList = currentData.map((d) => {
|
||||
const yearlyStats = [];
|
||||
const yearlyStats: SeweragePipeInputManholeStatusSaveYearlyStatItem[] = [];
|
||||
for (let yy = minYy; yy <= maxYy; yy++) {
|
||||
const year = 2000 + yy;
|
||||
const wholKey = `yy${yy}WholMnhlCnt`;
|
||||
@@ -1978,7 +2035,7 @@ const SewagePipeInp = () => {
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
key={(tabEnterableForGrid || isAdminInfo) ? "enterable" : "readonly"}
|
||||
key={tabKey}
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
|
||||
@@ -912,6 +912,16 @@ const SewagePipeMaintainInp = () => {
|
||||
}, [searchFormRemountKey]);
|
||||
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
const prevTabKey = activeTabRef.current;
|
||||
const latestRowsInPrevTab = getTabGridRows(prevTabKey);
|
||||
if (latestRowsInPrevTab.length > 0 || rowDataMap.has(prevTabKey)) {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(prevTabKey, latestRowsInPrevTab);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
activeTabRef.current = tabKey;
|
||||
setActiveTab(tabKey);
|
||||
if (!hasSearched || !lastSearchedValues || !searchParamRef.current) return;
|
||||
@@ -1110,11 +1120,7 @@ const SewagePipeMaintainInp = () => {
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
key={
|
||||
tabEnterableForGrid || isAdminInfo
|
||||
? `${tabKey}-enterable`
|
||||
: `${tabKey}-readonly`
|
||||
}
|
||||
key={tabKey}
|
||||
columnInfo={
|
||||
columnInfoByTab.get(tabKey) ??
|
||||
toGridColumnInfo(info)
|
||||
|
||||
@@ -4,10 +4,14 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
type RefObject,
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
|
||||
import { flushSync } from "react-dom";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { ValueType } from "realgrid";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
@@ -25,7 +29,6 @@ import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import type {
|
||||
CellEditedCallbackParams,
|
||||
CellStyleCallbackParams,
|
||||
CellTooltipCallbackParams,
|
||||
ColumnConfig,
|
||||
@@ -36,24 +39,33 @@ import { doList } from "@/components/util/column/input/DoList";
|
||||
import { RainfallPumpStationInpColumn } from "@/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn";
|
||||
import { SewageReservoirInpColumn } from "@/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { formatCompactYmdForGrid, normalizeOxYnValue } from "@/components/util/grid/ymdFormat";
|
||||
import {
|
||||
formatCompactYmdForGrid,
|
||||
normalizeOxYnValue,
|
||||
toCompactYmd,
|
||||
} from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputSewageReservoirPumpApi } from "@/entities/statistics/api/input/statistics-input-sewage-reservoir-pump-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import {
|
||||
SEWAGE_RESERVOIR_STATS_CD,
|
||||
type SewageReservoirItem,
|
||||
type SewageReservoirRequestParams,
|
||||
type SewageReservoirSaveItem,
|
||||
type RainfallPumpStationItem,
|
||||
type SewageReservoirVerificationResult,
|
||||
} from "@/entities/statistics/model/input/statistics-input-sewage-reservoir-pump-interface";
|
||||
import type {
|
||||
ApiResponse,
|
||||
cancelSubmitRequest,
|
||||
Sgg,
|
||||
SewerageStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
const SewageReservoirPumpInpTabList = [
|
||||
SewageReservoirInpColumn,
|
||||
RainfallPumpStationInpColumn,
|
||||
@@ -144,7 +156,7 @@ function buildSewageReservoirSearchParam(
|
||||
};
|
||||
}
|
||||
|
||||
/** 항목 추가 시 빈 행 (시도는 조회 시 선택한 시도명) */
|
||||
/** 항목 추가 시 빈 행 (`ctpvNm` = 조회 시도명) */
|
||||
function createEmptySewageReservoirRow(
|
||||
ctpvNm: string,
|
||||
sggNm = "",
|
||||
@@ -248,6 +260,108 @@ function mapSewageReservoirListToGridRows(
|
||||
});
|
||||
}
|
||||
|
||||
function nullableTrimmedString(
|
||||
value: string | number | null | undefined,
|
||||
): string | null {
|
||||
const s = String(value ?? "").trim();
|
||||
return s === "" ? null : s;
|
||||
}
|
||||
|
||||
function toGridNumber(value: string | number | null | undefined): number {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
/** API 조회 항목 → 저장/삭제 API 형식 (조회 직후 스냅샷용) */
|
||||
function buildSewageReservoirSaveItemFromApiItem(
|
||||
item: SewageReservoirItem,
|
||||
): SewageReservoirSaveItem {
|
||||
return buildSewageReservoirSaveItem(
|
||||
item as unknown as Record<string, string | number>,
|
||||
);
|
||||
}
|
||||
|
||||
function buildSewageReservoirDeleteList(
|
||||
loadedSnapshot: SewageReservoirSaveItem[],
|
||||
currentRows: Record<string, string | number>[],
|
||||
): SewageReservoirSaveItem[] {
|
||||
const currentFcltSnSet = new Set(
|
||||
currentRows
|
||||
.map((row) => String(row.fcltSn ?? "").trim())
|
||||
.filter((sn) => sn !== ""),
|
||||
);
|
||||
return loadedSnapshot.filter((item) => {
|
||||
const sn = String(item.fcltSn ?? "").trim();
|
||||
return sn !== "" && !currentFcltSnSet.has(sn);
|
||||
});
|
||||
}
|
||||
|
||||
/** 시군구명(`sggNm`) 기준 행정구역 코드·명 */
|
||||
function resolveSewageReservoirAdmdst(
|
||||
sggNm: string,
|
||||
sggOptions: Sgg[],
|
||||
): { admdstCd: string; admdstNm: string } {
|
||||
const trimmed = sggNm.trim();
|
||||
const matched = sggOptions.find((s) => s.gugun === trimmed);
|
||||
if (matched) {
|
||||
return { admdstCd: matched.adstDstrCd, admdstNm: matched.gugun };
|
||||
}
|
||||
return { admdstCd: "", admdstNm: trimmed };
|
||||
}
|
||||
|
||||
/** 그리드 행 → ST00040001 저장/삭제 API 항목 */
|
||||
function buildSewageReservoirSaveItem(
|
||||
row: Record<string, string | number>,
|
||||
sggOptions: Sgg[] = [],
|
||||
): SewageReservoirSaveItem {
|
||||
const { admdstCd, admdstNm } = resolveSewageReservoirAdmdst(
|
||||
String(row.sggNm ?? ""),
|
||||
sggOptions,
|
||||
);
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
fcltSn: String(row.fcltSn ?? ""),
|
||||
fcltNm: String(row.fcltNm ?? ""),
|
||||
fcltPstnCn: String(row.fcltPstnCn ?? ""),
|
||||
oprtngStrtYmd: toCompactYmd(row.oprtngStrtYmd),
|
||||
planDesignFreqCnt: toGridNumber(row.planDesignFreqCnt),
|
||||
fldngPrvtnYn: normalizeOxYnValue(row.fldngPrvtnYn),
|
||||
wqPlltnMtgtYn: normalizeOxYnValue(row.wqPlltnMtgtYn),
|
||||
rtlztnUsgYn: normalizeOxYnValue(row.rtlztnUsgYn),
|
||||
fcltStrctCn: nullableTrimmedString(row.fcltStrctCn),
|
||||
wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea),
|
||||
impdFcltCpct: toGridNumber(row.impdFcltCpct),
|
||||
grssPpqty: toGridNumber(row.grssPpqty),
|
||||
pumpFcltN1DmtrExpln: String(row.pumpFcltN1DmtrExpln ?? ""),
|
||||
pumpFcltN1PerPpqty: toGridNumber(row.pumpFcltN1PerPpqty),
|
||||
pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt),
|
||||
pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt),
|
||||
pumpFcltN2DmtrExpln: String(row.pumpFcltN2DmtrExpln ?? ""),
|
||||
pumpFcltN2PerPpqty: toGridNumber(row.pumpFcltN2PerPpqty),
|
||||
pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt),
|
||||
pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt),
|
||||
pumpFcltN3DmtrExpln: String(row.pumpFcltN3DmtrExpln ?? ""),
|
||||
pumpFcltN3PerPpqty: toGridNumber(row.pumpFcltN3PerPpqty),
|
||||
pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt),
|
||||
pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt),
|
||||
prcsgFcltYn: String(row.prcsgFcltYn ?? ""),
|
||||
ddrztnEqpmYn: String(row.ddrztnEqpmYn ?? ""),
|
||||
oslfEtgtYn: String(row.oslfEtgtYn ?? ""),
|
||||
mltvMthCn: nullableTrimmedString(row.mltvMthCn),
|
||||
impdMthCn: nullableTrimmedString(row.impdMthCn),
|
||||
impdPrcsMthdCn: nullableTrimmedString(row.impdPrcsMthdCn),
|
||||
dschgWtrsExpln: String(row.dschgWtrsExpln ?? ""),
|
||||
prcptnLastDspsQnt: toGridNumber(row.prcptnLastDspsQnt),
|
||||
ctmnLastDspsQnt: toGridNumber(row.ctmnLastDspsQnt),
|
||||
prcptnLastDspsMthdExpln: String(row.prcptnLastDspsMthdExpln ?? ""),
|
||||
ctmnLastDspsMthdExpln: String(row.ctmnLastDspsMthdExpln ?? ""),
|
||||
oprtngDayCnt: toGridNumber(row.oprtngDayCnt),
|
||||
grssMltvQnt: toGridNumber(row.grssMltvQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
|
||||
function mapRainfallPumpStationListToGridRows(
|
||||
list: RainfallPumpStationItem[],
|
||||
ctpvNm: string,
|
||||
@@ -275,6 +389,7 @@ function mapRainfallPumpStationListToGridRows(
|
||||
// 하수도 통계 관리 > 04. 하수저류 시설 및 빗물 펌프장
|
||||
const SewageReservoirPumpInp = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
const toolbarGroupAllowed = useStatisticsInputToolbarGroupGate();
|
||||
|
||||
@@ -282,24 +397,228 @@ const SewageReservoirPumpInp = () => {
|
||||
console.log("submit");
|
||||
};
|
||||
|
||||
const saveFunc = () => {
|
||||
console.log("save");
|
||||
};
|
||||
/** 마지막 조회 시 서버에 있던 하수저류시설 목록 (deleteList 비교 기준) */
|
||||
const loadedSewageReservoirSnapshotRef = useRef<SewageReservoirSaveItem[]>([]);
|
||||
|
||||
const cnacelSubmitFunc = () => {
|
||||
console.log("cancel submit");
|
||||
void (async () => {
|
||||
try {
|
||||
if (!(await confirm("제출된 내용을 취소하시겠습니까?"))) return;
|
||||
|
||||
if (!lastSearchedValues) {
|
||||
alert("먼저 조회를 진행해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
const tabKey = activeTabRef.current;
|
||||
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey;
|
||||
const userInfo = await getUserInfo();
|
||||
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
const response = await statisticsApi.cancelSubmit(requestData);
|
||||
|
||||
if (response.status === "success") {
|
||||
setIsLoading(true);
|
||||
await loadActiveTabData(tabKey, lastSearchedValues, false, false);
|
||||
alert("제출 취소되었습니다.");
|
||||
} else {
|
||||
alert(response.message ?? "제출 취소 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("제출 취소 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const isCheckedRow = (row: Record<string, string | number>): boolean => {
|
||||
const v = row.__checked;
|
||||
return v === 1 || v === "1" || v === "Y" || v === "y" || v === "true";
|
||||
};
|
||||
|
||||
const getCheckedItemIndexesFromGrid = (
|
||||
tabKey: string,
|
||||
rowCount: number,
|
||||
): number[] => {
|
||||
const gridRef =
|
||||
tabKey === SEWAGE_RESERVOIR_STATS_CD
|
||||
? reservoirGridRef
|
||||
: tabKey === rainfallPumpTabKey
|
||||
? rainfallPumpGridRef
|
||||
: null;
|
||||
if (!gridRef?.current?.gridView) return [];
|
||||
|
||||
const gridView = gridRef.current.gridView as {
|
||||
getCheckedItems?: () => number[];
|
||||
getCheckedRows?: () => number[];
|
||||
};
|
||||
|
||||
const checked =
|
||||
(typeof gridView.getCheckedItems === "function"
|
||||
? gridView.getCheckedItems()
|
||||
: typeof gridView.getCheckedRows === "function"
|
||||
? gridView.getCheckedRows()
|
||||
: []) ?? [];
|
||||
|
||||
return checked.filter(
|
||||
(idx) => Number.isInteger(idx) && idx >= 0 && idx < rowCount,
|
||||
);
|
||||
};
|
||||
|
||||
const getCurrentRowsForTab = (
|
||||
tabKey: string,
|
||||
): Record<string, string | number>[] => {
|
||||
if (tabKey === SEWAGE_RESERVOIR_STATS_CD) {
|
||||
return reservoirGridGetRowsRef.current?.() ?? rowDataMap.get(tabKey) ?? [];
|
||||
}
|
||||
if (tabKey === rainfallPumpTabKey) {
|
||||
return rainfallPumpGridGetRowsRef.current?.() ?? rowDataMap.get(tabKey) ?? [];
|
||||
}
|
||||
return rowDataMap.get(tabKey) ?? [];
|
||||
};
|
||||
|
||||
const commitGridForTab = (tabKey: string) => {
|
||||
const gridRef =
|
||||
tabKey === SEWAGE_RESERVOIR_STATS_CD
|
||||
? reservoirGridRef
|
||||
: tabKey === rainfallPumpTabKey
|
||||
? rainfallPumpGridRef
|
||||
: null;
|
||||
const gridView = gridRef?.current?.gridView as
|
||||
| {
|
||||
commit?: (force?: boolean) => void;
|
||||
cancel?: () => void;
|
||||
}
|
||||
| undefined;
|
||||
try {
|
||||
gridView?.commit?.(true);
|
||||
} catch {
|
||||
gridView?.cancel?.();
|
||||
}
|
||||
};
|
||||
|
||||
const deleteArticleFunc = () => {
|
||||
console.log("delete article");
|
||||
const tabKey = activeTabRef.current;
|
||||
const currentRows = getCurrentRowsForTab(tabKey);
|
||||
if (currentRows.length === 0) {
|
||||
alert("삭제할 데이터가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const checkedIndexes = getCheckedItemIndexesFromGrid(
|
||||
tabKey,
|
||||
currentRows.length,
|
||||
);
|
||||
const checkedIndexSet =
|
||||
checkedIndexes.length > 0
|
||||
? new Set(checkedIndexes)
|
||||
: new Set(
|
||||
currentRows
|
||||
.map((row, idx) => (isCheckedRow(row) ? idx : -1))
|
||||
.filter((idx) => idx >= 0),
|
||||
);
|
||||
|
||||
const nextRows = currentRows.filter((_, idx) => !checkedIndexSet.has(idx));
|
||||
if (nextRows.length === currentRows.length) {
|
||||
alert("삭제할 항목을 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, nextRows);
|
||||
return next;
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const saveFunc = () => {
|
||||
void (async () => {
|
||||
const tabKey = activeTabRef.current;
|
||||
if (tabKey !== SEWAGE_RESERVOIR_STATS_CD) {
|
||||
alert("저장할 수 없는 탭입니다.");
|
||||
return;
|
||||
}
|
||||
if (!lastSearchedValues) {
|
||||
alert("먼저 조회를 진행해주세요.");
|
||||
return;
|
||||
}
|
||||
if (!buildGugunOkForToolbar(lastSearchedValues)) {
|
||||
alert("시군구를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
if (!(await confirm("저장 하시겠습니까?"))) return;
|
||||
|
||||
commitGridForTab(tabKey);
|
||||
const noApplicable = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const currentData = getCurrentRowsForTab(tabKey);
|
||||
const saveList = noApplicable
|
||||
? []
|
||||
: currentData.map((row) =>
|
||||
buildSewageReservoirSaveItem(row, sggOptions),
|
||||
);
|
||||
const deleteList = buildSewageReservoirDeleteList(
|
||||
loadedSewageReservoirSnapshotRef.current,
|
||||
currentData,
|
||||
);
|
||||
|
||||
if (!noApplicable && saveList.length === 0 && deleteList.length === 0) {
|
||||
alert("저장할 변경 사항이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const response =
|
||||
await statisticsInputSewageReservoirPumpApi.saveSewageReservoir({
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
sggNm:
|
||||
lastSearchedValues.gugun === "ALL"
|
||||
? ""
|
||||
: (lastSearchedValues.gugun ?? ""),
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
...(noApplicable ? { noApplicable: true } : {}),
|
||||
saveList,
|
||||
deleteList,
|
||||
});
|
||||
|
||||
if (response.status === "success") {
|
||||
await loadActiveTabData(tabKey, lastSearchedValues, false, false);
|
||||
alert("저장되었습니다.");
|
||||
} else {
|
||||
alert(response.message ?? "저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const [activeTab, setActiveTab] = useState(firstTabKey);
|
||||
const [rowDataMap, setRowDataMap] = useState<
|
||||
Map<string, Record<string, string | number>[]>
|
||||
>(new Map());
|
||||
const [isNotApplicable, setIsNotApplicable] = useState(false);
|
||||
const [showIsNotApplicable, setShowIsNotApplicable] = useState(false);
|
||||
const [isNotApplicableByTab, setIsNotApplicableByTab] = useState<Map<string, boolean>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(
|
||||
null,
|
||||
@@ -317,13 +636,40 @@ const SewageReservoirPumpInp = () => {
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
||||
const reservoirGridRef = useRef<RealGridReact | null>(null);
|
||||
const rainfallPumpGridRef = useRef<RealGridReact | null>(null);
|
||||
const reservoirGridGetRowsRef =
|
||||
useRef<(() => Record<string, string | number>[]) | null>(null);
|
||||
const rainfallPumpGridGetRowsRef =
|
||||
useRef<(() => Record<string, string | number>[]) | null>(null);
|
||||
const sggOptionsSidoRef = useRef<string | null>(null);
|
||||
|
||||
const searchDoneRef = useRef(false);
|
||||
const periodEnterableRef = useRef(true);
|
||||
const activeTabRef = useRef(activeTab);
|
||||
const loadedTabKeysRef = useRef<Set<string>>(new Set());
|
||||
activeTabRef.current = activeTab;
|
||||
|
||||
const shouldShowNotApplicable = (
|
||||
tabKey: string,
|
||||
hasRows: boolean,
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
): boolean =>
|
||||
(tabKey === SEWAGE_RESERVOIR_STATS_CD || tabKey === rainfallPumpTabKey) &&
|
||||
hasSearched &&
|
||||
!hasRows &&
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable);
|
||||
|
||||
const setIsNotApplicableForTab = useCallback(
|
||||
(tabKey: string, nextValue: boolean) => {
|
||||
setIsNotApplicableByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, nextValue);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const commitToolbarSnapshotForLoadedTab = (
|
||||
tabKey: string,
|
||||
statusInfo: SewerageStatusInfo | null,
|
||||
@@ -371,8 +717,8 @@ const SewageReservoirPumpInp = () => {
|
||||
updateLastSearchedValues: boolean,
|
||||
overrides?: { mode?: string },
|
||||
): Promise<boolean> => {
|
||||
// 서버 데이터로 새로 불러오는 시점이므로 "해당없음" 체크는 항상 초기화
|
||||
setIsNotApplicable(false);
|
||||
// 서버 데이터로 새로 불러오는 시점이므로 현재 탭의 "해당없음" 체크는 초기화
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
if (tabKey === SEWAGE_RESERVOIR_STATS_CD) {
|
||||
const response =
|
||||
await statisticsInputSewageReservoirPumpApi.getSewageReservoirList(
|
||||
@@ -386,15 +732,14 @@ const SewageReservoirPumpInp = () => {
|
||||
|
||||
const { commonInfo, statusInfo, sewageReservoirList } = response.data;
|
||||
const list = sewageReservoirList ?? [];
|
||||
loadedSewageReservoirSnapshotRef.current = list
|
||||
.filter((item) => String(item.fcltSn ?? "").trim() !== "")
|
||||
.map(buildSewageReservoirSaveItemFromApiItem);
|
||||
const gridRows = mapSewageReservoirListToGridRows(
|
||||
list,
|
||||
commonInfo.ctpvNm,
|
||||
);
|
||||
|
||||
if (activeTabRef.current === tabKey) {
|
||||
setShowIsNotApplicable(list.length === 0);
|
||||
}
|
||||
|
||||
applyRowMap(mergeIntoExistingMap, (m) => {
|
||||
m.set(tabKey, gridRows);
|
||||
});
|
||||
@@ -409,6 +754,7 @@ const SewageReservoirPumpInp = () => {
|
||||
gridRows.length > 0,
|
||||
values,
|
||||
);
|
||||
loadedTabKeysRef.current.add(tabKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -444,6 +790,7 @@ const SewageReservoirPumpInp = () => {
|
||||
gridRows.length > 0,
|
||||
values,
|
||||
);
|
||||
loadedTabKeysRef.current.add(tabKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -490,6 +837,7 @@ const SewageReservoirPumpInp = () => {
|
||||
}, []);
|
||||
|
||||
const handleReset = () => {
|
||||
loadedSewageReservoirSnapshotRef.current = [];
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
@@ -499,10 +847,10 @@ const SewageReservoirPumpInp = () => {
|
||||
sggOptionsSidoRef.current = null;
|
||||
setHasSearched(false);
|
||||
setInputPeriodEnterable(true);
|
||||
setIsNotApplicable(false);
|
||||
setShowIsNotApplicable(false);
|
||||
setIsNotApplicableByTab(new Map());
|
||||
searchDoneRef.current = false;
|
||||
periodEnterableRef.current = true;
|
||||
loadedTabKeysRef.current.clear();
|
||||
setActiveTab(firstTabKey);
|
||||
};
|
||||
|
||||
@@ -521,6 +869,7 @@ const SewageReservoirPumpInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
loadedTabKeysRef.current.clear();
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
|
||||
@@ -529,6 +878,12 @@ const SewageReservoirPumpInp = () => {
|
||||
searchDoneRef.current = true;
|
||||
setHasSearched(true);
|
||||
setLastSearchedValues(values);
|
||||
const loadedSgg = await loadSggOptions(values.sido);
|
||||
sggOptionsSidoRef.current = values.sido;
|
||||
if (loadedSgg.length === 0) {
|
||||
alert("시군구 목록을 불러오지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const tabKeyToLoad = yearChangedFromLastSearch ? firstTabKey : activeTabRef.current;
|
||||
if (yearChangedFromLastSearch) {
|
||||
@@ -551,15 +906,21 @@ const SewageReservoirPumpInp = () => {
|
||||
};
|
||||
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
setActiveTab(tabKey);
|
||||
if (tabKey === SEWAGE_RESERVOIR_STATS_CD) {
|
||||
setShowIsNotApplicable(
|
||||
hasSearched && !tabRowDataExists(rowDataMap, tabKey),
|
||||
);
|
||||
} else {
|
||||
setShowIsNotApplicable(false);
|
||||
const prevTabKey = activeTabRef.current;
|
||||
commitGridForTab(prevTabKey);
|
||||
const latestRowsInPrevTab = getCurrentRowsForTab(prevTabKey);
|
||||
if (latestRowsInPrevTab.length > 0 || rowDataMap.has(prevTabKey)) {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(prevTabKey, latestRowsInPrevTab);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
activeTabRef.current = tabKey;
|
||||
setActiveTab(tabKey);
|
||||
if (!hasSearched || !lastSearchedValues) return;
|
||||
if (loadedTabKeysRef.current.has(tabKey)) return;
|
||||
if (tabRowDataExists(rowDataMap, tabKey)) return;
|
||||
|
||||
try {
|
||||
@@ -602,6 +963,26 @@ const SewageReservoirPumpInp = () => {
|
||||
activeTab === SEWAGE_RESERVOIR_STATS_CD || activeTab === rainfallPumpTabKey;
|
||||
const showArticleToolbarCard =
|
||||
hasSearched && isReservoirOrRainfallTab && toolbarGroupAllowed;
|
||||
const isNotApplicableForActiveTab = isNotApplicableByTab.get(activeTab) ?? false;
|
||||
const showNotApplicableForActiveTab =
|
||||
loadedTabKeysRef.current.has(activeTab) &&
|
||||
shouldShowNotApplicable(
|
||||
activeTab,
|
||||
tabRowDataExists(rowDataMap, activeTab),
|
||||
tabStatusInfoMap.get(activeTab),
|
||||
);
|
||||
const setIsNotApplicableForActiveTab: Dispatch<SetStateAction<boolean>> = useCallback(
|
||||
(next) => {
|
||||
const tabKey = activeTabRef.current;
|
||||
const current = isNotApplicableByTab.get(tabKey) ?? false;
|
||||
const resolved =
|
||||
typeof next === "function"
|
||||
? (next as (prevState: boolean) => boolean)(current)
|
||||
: next;
|
||||
setIsNotApplicableForTab(tabKey, resolved);
|
||||
},
|
||||
[isNotApplicableByTab, setIsNotApplicableForTab],
|
||||
);
|
||||
|
||||
const openVerificationHistoryDialog = () => {
|
||||
const ctx = lastSearchedValues ?? searchValues;
|
||||
@@ -673,8 +1054,6 @@ const SewageReservoirPumpInp = () => {
|
||||
// 행 추가 직후 setColumnProperty를 강제 재적용하면 타이밍 이슈로
|
||||
// `fieldIndex is out of bounds`가 발생할 수 있어 제거한다.
|
||||
|
||||
setShowIsNotApplicable(false);
|
||||
|
||||
const statusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, true, ctx);
|
||||
};
|
||||
@@ -690,6 +1069,13 @@ const SewageReservoirPumpInp = () => {
|
||||
) || isAdminInfo;
|
||||
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
|
||||
const columns = (tab.columns ?? []).map((col: ColumnConfig) => {
|
||||
if (isReservoirTab && col.fieldName === "ctpvNm") {
|
||||
return {
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? "rg-cell-readonly",
|
||||
};
|
||||
}
|
||||
if (isReservoirTab && col.fieldName === "sggNm") {
|
||||
return buildSggNmColumn(col, canEdit, sggOptions);
|
||||
}
|
||||
@@ -709,31 +1095,6 @@ const SewageReservoirPumpInp = () => {
|
||||
return map;
|
||||
}, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, sggOptions]);
|
||||
|
||||
const handleSewageReservoirCellEdited = useCallback(
|
||||
(params: CellEditedCallbackParams) => {
|
||||
if (params.fieldName !== "sggNm") return;
|
||||
const selected = sggOptions.find((s) => s.gugun === params.newValue);
|
||||
if (!selected) return;
|
||||
|
||||
const tabKey = SEWAGE_RESERVOIR_STATS_CD;
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const rows = [...(next.get(tabKey) ?? [])];
|
||||
const row = rows[params.itemIndex];
|
||||
if (!row) return prev;
|
||||
rows[params.itemIndex] = {
|
||||
...row,
|
||||
sggNm: selected.gugun,
|
||||
admdstCd: selected.adstDstrCd,
|
||||
admdstNm: selected.gugun,
|
||||
};
|
||||
next.set(tabKey, rows);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[sggOptions],
|
||||
);
|
||||
|
||||
const verificationCellStyle = ({
|
||||
fieldName,
|
||||
row,
|
||||
@@ -817,10 +1178,10 @@ const SewageReservoirPumpInp = () => {
|
||||
{showArticleToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
||||
{showIsNotApplicable && (
|
||||
{showNotApplicableForActiveTab && (
|
||||
<NotApplicableCheckButton
|
||||
isCheck={isNotApplicable}
|
||||
setIsCheck={setIsNotApplicable}
|
||||
isCheck={isNotApplicableForActiveTab}
|
||||
setIsCheck={setIsNotApplicableForActiveTab}
|
||||
/>
|
||||
)}
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
@@ -859,13 +1220,13 @@ const SewageReservoirPumpInp = () => {
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
key={
|
||||
tabEnterableForGrid || isAdminInfo
|
||||
? `${tabKey}-enterable`
|
||||
: `${tabKey}-readonly`
|
||||
}
|
||||
key={tabKey}
|
||||
getGridRef={
|
||||
isReservoirTab ? reservoirGridRef : undefined
|
||||
isReservoirTab
|
||||
? reservoirGridRef
|
||||
: tabKey === rainfallPumpTabKey
|
||||
? rainfallPumpGridRef
|
||||
: undefined
|
||||
}
|
||||
columnInfo={
|
||||
columnInfoByTab.get(tabKey) ??
|
||||
@@ -894,6 +1255,13 @@ const SewageReservoirPumpInp = () => {
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
getGridDataRef={
|
||||
tabKey === SEWAGE_RESERVOIR_STATS_CD
|
||||
? reservoirGridGetRowsRef
|
||||
: tabKey === rainfallPumpTabKey
|
||||
? rainfallPumpGridGetRowsRef
|
||||
: undefined
|
||||
}
|
||||
showHeaderCheckbox
|
||||
statusInfo={tabStatusInfo}
|
||||
isExcelImport={
|
||||
@@ -903,13 +1271,6 @@ const SewageReservoirPumpInp = () => {
|
||||
}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
onCellEdited={
|
||||
isReservoirTab &&
|
||||
tabEnterableForGrid &&
|
||||
sggOptions.length > 0
|
||||
? handleSewageReservoirCellEdited
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
@@ -921,6 +1282,7 @@ const SewageReservoirPumpInp = () => {
|
||||
onOpenChange={setVerificationHistoryOpen}
|
||||
queryContext={verificationQueryContext}
|
||||
/>
|
||||
<ConfirmDialog isOpen={false} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -196,6 +196,20 @@ const LogHistory = () => {
|
||||
|
||||
const handleSearch = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (startDate && endDate) {
|
||||
const start = dayjs(startDate);
|
||||
const end = dayjs(endDate);
|
||||
if (start.isAfter(end, "day")) {
|
||||
alert("시작일은 종료일보다 늦을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
if (end.isBefore(start, "day")) {
|
||||
alert("종료일은 시작일보다 빠를 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await loadLogs(1);
|
||||
};
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ function buildRowsFromStatusList(
|
||||
statKeys: string[],
|
||||
): Record<string, string | number>[] {
|
||||
const rowMap = new Map<string, Record<string, string | number>>();
|
||||
const rowOrder: string[] = [];
|
||||
|
||||
for (const item of statusList) {
|
||||
const { sido, sgg } = splitSidoSggNm(item.sidoSggNm);
|
||||
@@ -228,6 +229,7 @@ function buildRowsFromStatusList(
|
||||
row[key] = "";
|
||||
}
|
||||
rowMap.set(rowKey, row);
|
||||
rowOrder.push(rowKey);
|
||||
}
|
||||
|
||||
const row = rowMap.get(rowKey)!;
|
||||
@@ -236,11 +238,7 @@ function buildRowsFromStatusList(
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(rowMap.values()).sort((a, b) => {
|
||||
const sidoCmp = String(a[SIDO_FIELD]).localeCompare(String(b[SIDO_FIELD]), "ko");
|
||||
if (sidoCmp !== 0) return sidoCmp;
|
||||
return String(a[SGG_FIELD]).localeCompare(String(b[SGG_FIELD]), "ko");
|
||||
});
|
||||
return rowOrder.map((key) => rowMap.get(key)!);
|
||||
}
|
||||
|
||||
function buildUnSubmitDataRequest(
|
||||
|
||||
@@ -149,6 +149,14 @@ export type ComputedFieldConfig =
|
||||
denominatorFields: string[];
|
||||
thirdFields: string[];
|
||||
type: "swrgInstlrtRate";
|
||||
}
|
||||
| {
|
||||
pumpGroups: Array<{
|
||||
pumpCntField: string;
|
||||
rsrvPumpCntField: string;
|
||||
perPpqtyField: string;
|
||||
}>;
|
||||
type: "pumpGrossPpqty";
|
||||
};
|
||||
|
||||
export type ColumnConfig = {
|
||||
|
||||
@@ -8,69 +8,74 @@ import { ValueType } from "realgrid";
|
||||
export const PumpingStationInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'sido', fieldName: '시도', header: { text: '시도' }, editable: false },
|
||||
{ name: 'sgg', fieldName: '시군구', header: { text: '시군구' }, footer: { text: '합계' } },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
|
||||
// 기본 정보
|
||||
{ name: 'treatmentFacilityName', fieldName: '처리장명', header: { text: '처리장명' } },
|
||||
{ name: 'facilityName', fieldName: '시설명', header: { text: '시설명' } },
|
||||
{ name: 'classification', fieldName: '구분(합류식 분류식)', header: { text: '구분(합류식 분류식)' } },
|
||||
{ name: 'location', fieldName: '위치', header: { text: '위치' } },
|
||||
{ name: 'operationDate', fieldName: '가동 년월일 yyyy-mm-dd', header: { text: '가동 년월일 yyyy-mm-dd' } },
|
||||
{ name: 'planDrainageArea', fieldName: '계획배수면적(㎢)', header: { text: '계획배수면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' } },
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
|
||||
{ name: 'pumpSeNm', fieldName: 'pumpSeNm', header: { text: '구분(합류식 분류식)' } },
|
||||
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '위치' } },
|
||||
{ name: 'oprtngStrtYmd', fieldName: 'oprtngStrtYmd', header: { text: '가동 년월일 yyyy-mm-dd' } },
|
||||
{ name: 'planMltvArea', fieldName: 'planMltvArea', header: { text: '계획배수면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
|
||||
|
||||
// 계획시간 대 최대배수량
|
||||
{ name: 'fairWeatherFlow', fieldName: '청천시(A)', header: { text: '청천시(A)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'rainyWeatherFlow', fieldName: '우천시(B)', header: { text: '우천시(B)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'rainwaterFlow', fieldName: '우수량(B-A)', header: { text: '우수량(B-A)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'sndyArea', fieldName: 'sndyArea', header: { text: '청천시(A)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'rainArea', fieldName: 'rainArea', header: { text: '우천시(B)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'exlnQnt', fieldName: 'exlnQnt', header: { text: '우수량(B-A)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
|
||||
// 배수능력 > 총 양수량 (오수)
|
||||
{ name: 'sewageTotalPumpCapacity', fieldName: '총양수량(m³/min)', header: { text: '총양수량(m³/min)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'drtwPumpGrssPpqty', fieldName: 'drtwPumpGrssPpqty', header: { text: '총양수량(m³/min)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
|
||||
// 배수능력 > 오수펌프(1)
|
||||
{ name: 'sewagePump1Diameter', fieldName: '오수펌프(1) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'sewagePump1Capacity', fieldName: '오수펌프(1) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'sewagePump1Count', fieldName: '오수펌프(1) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'sewagePump1Reserve', fieldName: '오수펌프(1) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'drtwPumpN1DmtrArea', fieldName: 'drtwPumpN1DmtrArea', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'drtwPumpN1Ppqty', fieldName: 'drtwPumpN1Ppqty', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'drtwPumpN1Cnt', fieldName: 'drtwPumpN1Cnt', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'drtwPumpN1RsrvCnt', fieldName: 'drtwPumpN1RsrvCnt', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 배수능력 > 오수펌프(2)
|
||||
{ name: 'sewagePump2Diameter', fieldName: '오수펌프(2) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'sewagePump2Capacity', fieldName: '오수펌프(2) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'sewagePump2Count', fieldName: '오수펌프(2) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'sewagePump2Reserve', fieldName: '오수펌프(2) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'drtwPumpN2DmtrArea', fieldName: 'drtwPumpN2DmtrArea', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'drtwPumpN2Ppqty', fieldName: 'drtwPumpN2Ppqty', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'drtwPumpN2Cnt', fieldName: 'drtwPumpN2Cnt', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'drtwPumpN2RsrvCnt', fieldName: 'drtwPumpN2RsrvCnt', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 배수능력 > 오수펌프(3)
|
||||
{ name: 'sewagePump3Diameter', fieldName: '오수펌프(3) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'sewagePump3Capacity', fieldName: '오수펌프(3) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'sewagePump3Count', fieldName: '오수펌프(3) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'sewagePump3Reserve', fieldName: '오수펌프(3) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'drtwPumpN3DmtrArea', fieldName: 'drtwPumpN3DmtrArea', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'drtwPumpN3Ppqty', fieldName: 'drtwPumpN3Ppqty', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'drtwPumpN3Cnt', fieldName: 'drtwPumpN3Cnt', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'drtwPumpN3RsrvCnt', fieldName: 'drtwPumpN3RsrvCnt', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 배수능력 > 총 양수량 (우수)
|
||||
{ name: 'rainwaterTotalPumpCapacity', fieldName: '총양수량(m³/min)', header: { text: '총양수량(m³/min)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'exlnPumpGrssPpqty', fieldName: 'exlnPumpGrssPpqty', header: { text: '총양수량(m³/min)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
|
||||
// 배수능력 > 우수펌프(1)
|
||||
{ name: 'rainwaterPump1Diameter', fieldName: '우수펌프(1) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'rainwaterPump1Capacity', fieldName: '우수펌프(1) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'rainwaterPump1Count', fieldName: '우수펌프(1) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'rainwaterPump1Reserve', fieldName: '우수펌프(1) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'exlnPumpN1DmtrArea', fieldName: 'exlnPumpN1DmtrArea', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'exlnPumpN1Ppqty', fieldName: 'exlnPumpN1Ppqty', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'exlnPumpN1Cnt', fieldName: 'exlnPumpN1Cnt', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'exlnPumpN1RsrvCnt', fieldName: 'exlnPumpN1RsrvCnt', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 배수능력 > 우수펌프(2)
|
||||
{ name: 'rainwaterPump2Diameter', fieldName: '우수펌프(2) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'rainwaterPump2Capacity', fieldName: '우수펌프(2) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'rainwaterPump2Count', fieldName: '우수펌프(2) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'rainwaterPump2Reserve', fieldName: '우수펌프(2) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'exlnPumpN2DmtrArea', fieldName: 'exlnPumpN2DmtrArea', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'exlnPumpN2Ppqty', fieldName: 'exlnPumpN2Ppqty', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'exlnPumpN2Cnt', fieldName: 'exlnPumpN2Cnt', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'exlnPumpN2RsrvCnt', fieldName: 'exlnPumpN2RsrvCnt', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 배수능력 > 우수펌프(3)
|
||||
{ name: 'rainwaterPump3Diameter', fieldName: '우수펌프(3) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'rainwaterPump3Capacity', fieldName: '우수펌프(3) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'rainwaterPump3Count', fieldName: '우수펌프(3) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'rainwaterPump3Reserve', fieldName: '우수펌프(3) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'exlnPumpN3DmtrArea', fieldName: 'exlnPumpN3DmtrArea', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'exlnPumpN3Ppqty', fieldName: 'exlnPumpN3Ppqty', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'exlnPumpN3Cnt', fieldName: 'exlnPumpN3Cnt', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'exlnPumpN3RsrvCnt', fieldName: 'exlnPumpN3RsrvCnt', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 연간전력 사용량
|
||||
{ name: 'annualPowerUsage', fieldName: '연간전력사용량(kWh/년)', header: { text: '연간전력사용량(kWh/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{ name: 'anlElpwrUseQnt', fieldName: 'anlElpwrUseQnt', header: { text: '연간전력사용량(kWh/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
|
||||
// 비고
|
||||
{ name: 'remarks', fieldName: '비고', header: { text: '비고' } },
|
||||
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } },
|
||||
|
||||
// Hidden Columns : 시스템 관리를 위한 값
|
||||
{ name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, },
|
||||
{ name: 'fcltSn', fieldName: 'fcltSn', header: { text: '시설일련번호' }, visible: false, },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false },
|
||||
],
|
||||
|
||||
columnLayout: [
|
||||
@@ -78,66 +83,69 @@ export const PumpingStationInpColumn = {
|
||||
name: 'administrativeArea',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '행정구역' },
|
||||
items: ['sido', 'sgg'],
|
||||
items: ['ctpvNm', 'sggNm'],
|
||||
},
|
||||
'treatmentFacilityName',
|
||||
'facilityName',
|
||||
'classification',
|
||||
'location',
|
||||
'operationDate',
|
||||
'planDrainageArea',
|
||||
'swtrmtFcltNm',
|
||||
'fcltNm',
|
||||
'pumpSeNm',
|
||||
'fcltPstnCn',
|
||||
'oprtngStrtYmd',
|
||||
'planMltvArea',
|
||||
{
|
||||
name: 'planFlowGroup',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '계획시간대최대배수량(m³/hr)' },
|
||||
items: ['fairWeatherFlow', 'rainyWeatherFlow', 'rainwaterFlow'],
|
||||
items: ['sndyArea', 'rainArea', 'exlnQnt'],
|
||||
},
|
||||
{
|
||||
name: 'drainageCapability',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '배수능력' },
|
||||
items: [
|
||||
'sewageTotalPumpCapacity',
|
||||
'drtwPumpGrssPpqty',
|
||||
{
|
||||
name: 'sewagePump1Group',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '오수펌프(1)' },
|
||||
items: ['sewagePump1Diameter', 'sewagePump1Capacity', 'sewagePump1Count', 'sewagePump1Reserve'],
|
||||
items: ['drtwPumpN1DmtrArea', 'drtwPumpN1Ppqty', 'drtwPumpN1Cnt', 'drtwPumpN1RsrvCnt'],
|
||||
},
|
||||
{
|
||||
name: 'sewagePump2Group',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '오수펌프(2)' },
|
||||
items: ['sewagePump2Diameter', 'sewagePump2Capacity', 'sewagePump2Count', 'sewagePump2Reserve'],
|
||||
items: ['drtwPumpN2DmtrArea', 'drtwPumpN2Ppqty', 'drtwPumpN2Cnt', 'drtwPumpN2RsrvCnt'],
|
||||
},
|
||||
{
|
||||
name: 'sewagePump3Group',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '오수펌프(3)' },
|
||||
items: ['sewagePump3Diameter', 'sewagePump3Capacity', 'sewagePump3Count', 'sewagePump3Reserve'],
|
||||
items: ['drtwPumpN3DmtrArea', 'drtwPumpN3Ppqty', 'drtwPumpN3Cnt', 'drtwPumpN3RsrvCnt'],
|
||||
},
|
||||
'rainwaterTotalPumpCapacity',
|
||||
'exlnPumpGrssPpqty',
|
||||
{
|
||||
name: 'rainwaterPump1Group',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '우수펌프(1)' },
|
||||
items: ['rainwaterPump1Diameter', 'rainwaterPump1Capacity', 'rainwaterPump1Count', 'rainwaterPump1Reserve'],
|
||||
items: ['exlnPumpN1DmtrArea', 'exlnPumpN1Ppqty', 'exlnPumpN1Cnt', 'exlnPumpN1RsrvCnt'],
|
||||
},
|
||||
{
|
||||
name: 'rainwaterPump2Group',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '우수펌프(2)' },
|
||||
items: ['rainwaterPump2Diameter', 'rainwaterPump2Capacity', 'rainwaterPump2Count', 'rainwaterPump2Reserve'],
|
||||
items: ['exlnPumpN2DmtrArea', 'exlnPumpN2Ppqty', 'exlnPumpN2Cnt', 'exlnPumpN2RsrvCnt'],
|
||||
},
|
||||
{
|
||||
name: 'rainwaterPump3Group',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '우수펌프(3)' },
|
||||
items: ['rainwaterPump3Diameter', 'rainwaterPump3Capacity', 'rainwaterPump3Count', 'rainwaterPump3Reserve'],
|
||||
items: ['exlnPumpN3DmtrArea', 'exlnPumpN3Ppqty', 'exlnPumpN3Cnt', 'exlnPumpN3RsrvCnt'],
|
||||
},
|
||||
],
|
||||
},
|
||||
'annualPowerUsage',
|
||||
'remarks',
|
||||
'anlElpwrUseQnt',
|
||||
'rmrk',
|
||||
'admdstCd',
|
||||
'fcltSn',
|
||||
'admdstNm'
|
||||
]
|
||||
};
|
||||
+77
-6
@@ -1,6 +1,33 @@
|
||||
import type { ColumnConfig } from "@/components/type/DataGrid.type";
|
||||
import { footerInt, footerDecimal1, footerDecimal2, footerDecimal3 } from "@/components/util/grid/footerSummary";
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
const RAINFALL_PUMP_FCLTY_KIND_VALUES = ["해당없음", "제1종", "제2종", "제3종"] as const;
|
||||
const RAINFALL_PUMP_YN_VALUES = ["유", "무"] as const;
|
||||
|
||||
function dropdownColumn(
|
||||
col: Pick<ColumnConfig, "name" | "fieldName" | "header"> &
|
||||
Partial<ColumnConfig>,
|
||||
options: readonly string[],
|
||||
): ColumnConfig {
|
||||
const values = [...options];
|
||||
return {
|
||||
...col,
|
||||
values,
|
||||
labels: values,
|
||||
lookupDisplay: true,
|
||||
editor: {
|
||||
type: "dropdown",
|
||||
domainOnly: true,
|
||||
textReadOnly: true,
|
||||
commitOnSelect: true,
|
||||
dropDownWhenClick: true,
|
||||
values,
|
||||
labels: values,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 하수도 통계 관리 > 04.하수저류시설 및 빗물펌프장 > tab 2: 빗물펌프장
|
||||
export const RainfallPumpStationInpColumn = {
|
||||
columns: [
|
||||
@@ -10,7 +37,10 @@ export const RainfallPumpStationInpColumn = {
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
|
||||
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '위치' } },
|
||||
|
||||
{ name: 'fcltyKndCn', fieldName: 'fcltyKndCn', header: { text: '시설물종류' } },
|
||||
dropdownColumn(
|
||||
{ name: 'fcltyKndCn', fieldName: 'fcltyKndCn', header: { text: '시설물종류' } },
|
||||
RAINFALL_PUMP_FCLTY_KIND_VALUES,
|
||||
),
|
||||
|
||||
{
|
||||
name: 'oprtngStrtYmd',
|
||||
@@ -29,14 +59,46 @@ export const RainfallPumpStationInpColumn = {
|
||||
{ name: 'wtrStrgZoneArea', fieldName: 'wtrStrgZoneArea', header: { text: '집수구역면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
|
||||
|
||||
// 유수지
|
||||
{ name: 'rsvrYn', fieldName: 'rsvrYn', header: { text: '유수지유무(유,무)' } },
|
||||
dropdownColumn(
|
||||
{ name: 'rsvrYn', fieldName: 'rsvrYn', header: { text: '유수지유무(유,무)' } },
|
||||
RAINFALL_PUMP_YN_VALUES,
|
||||
),
|
||||
{ name: 'rsvrImpndQnt', fieldName: 'rsvrImpndQnt', header: { text: '저류랑(m³)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
|
||||
// 흡수정
|
||||
{ name: 'abstnRgnImpdQnt', fieldName: 'abstnRgnImpdQnt', header: { text: '저류랑(m³)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
|
||||
// 펌프시설 > 총양수량(m³/min)
|
||||
{ name: 'grssPpqty', fieldName: 'grssPpqty', header: { text: '총양수량(m³/min)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{
|
||||
name: 'grssPpqty',
|
||||
fieldName: 'grssPpqty',
|
||||
header: { text: '총양수량(m³/min)' },
|
||||
editable: false,
|
||||
footer: footerDecimal1,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: '#,##0.0',
|
||||
styleName: 'rg-cell-total',
|
||||
computed: {
|
||||
type: 'pumpGrossPpqty' as const,
|
||||
pumpGroups: [
|
||||
{
|
||||
pumpCntField: 'pumpFcltN1PumpCnt',
|
||||
rsrvPumpCntField: 'pumpFcltN1RsrvPumpCnt',
|
||||
perPpqtyField: 'pumpFcltN1PerPpqty',
|
||||
},
|
||||
{
|
||||
pumpCntField: 'pumpFcltN2PumpCnt',
|
||||
rsrvPumpCntField: 'pumpFcltN2RsrvPumpCnt',
|
||||
perPpqtyField: 'pumpFcltN2PerPpqty',
|
||||
},
|
||||
{
|
||||
pumpCntField: 'pumpFcltN3PumpCnt',
|
||||
rsrvPumpCntField: 'pumpFcltN3RsrvPumpCnt',
|
||||
perPpqtyField: 'pumpFcltN3PerPpqty',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// 펌프시설1
|
||||
{ name: 'pumpFcltN1DmtrExpln', fieldName: 'pumpFcltN1DmtrExpln', header: { text: '구경(mm)' } },
|
||||
@@ -57,9 +119,18 @@ export const RainfallPumpStationInpColumn = {
|
||||
{ name: 'pumpFcltN3RsrvPumpCnt', fieldName: 'pumpFcltN3RsrvPumpCnt', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 부대시설
|
||||
{ name: 'prcsgFcltYn', fieldName: 'prcsgFcltYn', header: { text: '전처리시설유무(유/무)' } },
|
||||
{ name: 'ddrztnEqpmYn', fieldName: 'ddrztnEqpmYn', header: { text: '탈취설비유무(유/무)' } },
|
||||
{ name: 'oslfEtgtYn', fieldName: 'oslfEtgtYn', header: { text: '자가발전유무(유/무)' } },
|
||||
dropdownColumn(
|
||||
{ name: 'prcsgFcltYn', fieldName: 'prcsgFcltYn', header: { text: '전처리시설유무(유/무)' } },
|
||||
RAINFALL_PUMP_YN_VALUES,
|
||||
),
|
||||
dropdownColumn(
|
||||
{ name: 'ddrztnEqpmYn', fieldName: 'ddrztnEqpmYn', header: { text: '탈취설비유무(유/무)' } },
|
||||
RAINFALL_PUMP_YN_VALUES,
|
||||
),
|
||||
dropdownColumn(
|
||||
{ name: 'oslfEtgtYn', fieldName: 'oslfEtgtYn', header: { text: '자가발전유무(유/무)' } },
|
||||
RAINFALL_PUMP_YN_VALUES,
|
||||
),
|
||||
|
||||
// 방류수역
|
||||
{ name: 'dschgWtrsExpln', fieldName: 'dschgWtrsExpln', header: { text: '방류수역' } },
|
||||
|
||||
+84
-9
@@ -11,6 +11,10 @@ export const SEWAGE_RESERVOIR_FCLT_STRCT_VALUES = [
|
||||
"지하터널식",
|
||||
"기타",
|
||||
] as const;
|
||||
export const SEWAGE_RESERVOIR_YN_VALUES = ["유", "무"] as const;
|
||||
export const SEWAGE_RESERVOIR_MLTV_MTH_VALUES = ["자연배수", "강제배수"] as const;
|
||||
export const SEWAGE_RESERVOIR_IMPD_MTH_VALUES = ["직렬", "병렬"] as const;
|
||||
export const SEWAGE_RESERVOIR_IMPD_PRCS_MTHD_VALUES = ["자체처리", "연계처리"] as const;
|
||||
|
||||
function dropdownColumn(
|
||||
col: Pick<ColumnConfig, "name" | "fieldName" | "header"> &
|
||||
@@ -99,7 +103,36 @@ export const SewageReservoirInpColumn = {
|
||||
{ name: 'impdFcltCpct', fieldName: 'impdFcltCpct', header: { text: '시설용량(m³)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
|
||||
// 펌프시설 > 총양수량
|
||||
{ name: 'grssPpqty', fieldName: 'grssPpqty', header: { text: '총양수량(m³/min)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
{
|
||||
name: 'grssPpqty',
|
||||
fieldName: 'grssPpqty',
|
||||
header: { text: '총양수량(m³/min)' },
|
||||
editable: false,
|
||||
footer: footerDecimal1,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: '#,##0.0',
|
||||
styleName: 'rg-cell-total',
|
||||
computed: {
|
||||
type: 'pumpGrossPpqty' as const,
|
||||
pumpGroups: [
|
||||
{
|
||||
pumpCntField: 'pumpFcltN1PumpCnt',
|
||||
rsrvPumpCntField: 'pumpFcltN1RsrvPumpCnt',
|
||||
perPpqtyField: 'pumpFcltN1PerPpqty',
|
||||
},
|
||||
{
|
||||
pumpCntField: 'pumpFcltN2PumpCnt',
|
||||
rsrvPumpCntField: 'pumpFcltN2RsrvPumpCnt',
|
||||
perPpqtyField: 'pumpFcltN2PerPpqty',
|
||||
},
|
||||
{
|
||||
pumpCntField: 'pumpFcltN3PumpCnt',
|
||||
rsrvPumpCntField: 'pumpFcltN3RsrvPumpCnt',
|
||||
perPpqtyField: 'pumpFcltN3PerPpqty',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// 펌프시설 1
|
||||
{ name: 'pumpFcltN1DmtrExpln', fieldName: 'pumpFcltN1DmtrExpln', header: { text: '구경(mm)' } },
|
||||
@@ -120,18 +153,60 @@ export const SewageReservoirInpColumn = {
|
||||
{ name: 'pumpFcltN3RsrvPumpCnt', fieldName: 'pumpFcltN3RsrvPumpCnt', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 부대시설
|
||||
{ name: 'prcsgFcltYn', fieldName: 'prcsgFcltYn', header: { text: '전처리시설유무(유/무)' } },
|
||||
{ name: 'ddrztnEqpmYn', fieldName: 'ddrztnEqpmYn', header: { text: '탈취설비유무(유/무)' } },
|
||||
{ name: 'oslfEtgtYn', fieldName: 'oslfEtgtYn', header: { text: '자가발전유무(유/무)' } },
|
||||
dropdownColumn(
|
||||
{
|
||||
name: "prcsgFcltYn",
|
||||
fieldName: "prcsgFcltYn",
|
||||
header: { text: "전처리시설유무(유/무)" },
|
||||
},
|
||||
SEWAGE_RESERVOIR_YN_VALUES,
|
||||
),
|
||||
dropdownColumn(
|
||||
{
|
||||
name: "ddrztnEqpmYn",
|
||||
fieldName: "ddrztnEqpmYn",
|
||||
header: { text: "탈취설비유무(유/무)" },
|
||||
},
|
||||
SEWAGE_RESERVOIR_YN_VALUES,
|
||||
),
|
||||
dropdownColumn(
|
||||
{
|
||||
name: "oslfEtgtYn",
|
||||
fieldName: "oslfEtgtYn",
|
||||
header: { text: "자가발전유무(유/무)" },
|
||||
},
|
||||
SEWAGE_RESERVOIR_YN_VALUES,
|
||||
),
|
||||
|
||||
// 배수 방식
|
||||
{ name: 'mltvMthCn', fieldName: 'mltvMthCn', header: { text: '배수방식(자연배수,강제배수)' } },
|
||||
|
||||
dropdownColumn(
|
||||
{
|
||||
name: "mltvMthCn",
|
||||
fieldName: "mltvMthCn",
|
||||
header: { text: "배수방식(자연배수,강제배수)" },
|
||||
},
|
||||
SEWAGE_RESERVOIR_MLTV_MTH_VALUES,
|
||||
),
|
||||
|
||||
// 저류 방식
|
||||
{ name: 'impdMthCn', fieldName: 'impdMthCn', header: { text: '저류방식(직렬,병렬)' } },
|
||||
|
||||
dropdownColumn(
|
||||
{
|
||||
name: "impdMthCn",
|
||||
fieldName: "impdMthCn",
|
||||
header: { text: "저류방식(직렬,병렬)" },
|
||||
},
|
||||
SEWAGE_RESERVOIR_IMPD_MTH_VALUES,
|
||||
),
|
||||
|
||||
// 저류수 처리방법
|
||||
{ name: 'impdPrcsMthdCn', fieldName: 'impdPrcsMthdCn', header: { text: '저류수처리방법(자체처리,연계처리)' } },
|
||||
dropdownColumn(
|
||||
{
|
||||
name: "impdPrcsMthdCn",
|
||||
fieldName: "impdPrcsMthdCn",
|
||||
header: { text: "저류수처리방법(자체처리,연계처리)" },
|
||||
},
|
||||
SEWAGE_RESERVOIR_IMPD_PRCS_MTHD_VALUES,
|
||||
),
|
||||
|
||||
// 방류 수역
|
||||
{ name: 'dschgWtrsExpln', fieldName: 'dschgWtrsExpln', header: { text: '방류수역(또는 연계처리장명)' } },
|
||||
|
||||
@@ -179,6 +179,29 @@ export function createSwrgInstlrtRateValueCallback(
|
||||
};
|
||||
}
|
||||
|
||||
/** 총양수량: Σ(펌프대수 + 예비펌프대수) × 대당 양수량 */
|
||||
export function createPumpGrossPpqtyValueCallback(
|
||||
pumpGroups: Array<{
|
||||
pumpCntField: string;
|
||||
rsrvPumpCntField: string;
|
||||
perPpqtyField: string;
|
||||
}>,
|
||||
): DataFieldValueCallback {
|
||||
return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => {
|
||||
let total = 0;
|
||||
for (const group of pumpGroups) {
|
||||
const pumpIdx = fieldNamesArr.indexOf(group.pumpCntField);
|
||||
const rsrvIdx = fieldNamesArr.indexOf(group.rsrvPumpCntField);
|
||||
const perIdx = fieldNamesArr.indexOf(group.perPpqtyField);
|
||||
const pumpCnt = pumpIdx >= 0 ? toNum(values[pumpIdx]) : 0;
|
||||
const rsrvCnt = rsrvIdx >= 0 ? toNum(values[rsrvIdx]) : 0;
|
||||
const perPpqty = perIdx >= 0 ? toNum(values[perIdx]) : 0;
|
||||
total += (pumpCnt + rsrvCnt) * perPpqty;
|
||||
}
|
||||
return total;
|
||||
};
|
||||
}
|
||||
|
||||
/** 연산 타입에 맞는 valueCallback 반환 */
|
||||
export function getComputedValueCallback(config: ComputedFieldConfig): DataFieldValueCallback {
|
||||
switch (config.type) {
|
||||
@@ -209,5 +232,7 @@ export function getComputedValueCallback(config: ComputedFieldConfig): DataField
|
||||
return createCoverageRateValueCallback(config.numeratorFields, config.denominatorFields);
|
||||
case 'swrgInstlrtRate':
|
||||
return createSwrgInstlrtRateValueCallback(config.numeratorFields, config.denominatorFields, config.thirdFields);
|
||||
case 'pumpGrossPpqty':
|
||||
return createPumpGrossPpqtyValueCallback(config.pumpGroups);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,61 @@ export function formatCompactYmdForGrid(ymd: string | number | null | undefined)
|
||||
return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`;
|
||||
}
|
||||
|
||||
/** 그리드 값 → API 8자리 일자(yyyyMMdd) */
|
||||
export function toCompactYmd(value: string | number | null | undefined): string {
|
||||
const s = String(value ?? "").replace(/\D/g, "");
|
||||
if (s.length !== 8) return "";
|
||||
return s;
|
||||
function isValidCompactYmdDigits(ymd: string): boolean {
|
||||
if (!/^\d{8}$/.test(ymd)) return false;
|
||||
const y = Number(ymd.slice(0, 4));
|
||||
const m = Number(ymd.slice(4, 6));
|
||||
const d = Number(ymd.slice(6, 8));
|
||||
if (y < 1000 || y > 9999 || m < 1 || m > 12 || d < 1 || d > 31) return false;
|
||||
const dt = new Date(y, m - 1, d);
|
||||
return dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d;
|
||||
}
|
||||
|
||||
function formatDateToCompactYmd(date: Date): string {
|
||||
if (Number.isNaN(date.getTime())) return "";
|
||||
const y = date.getFullYear();
|
||||
const m = date.getMonth() + 1;
|
||||
const d = date.getDate();
|
||||
if (y < 1000 || y > 9999) return "";
|
||||
return `${y}${String(m).padStart(2, "0")}${String(d).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 그리드·API 값 → 8자리 일자(yyyyMMdd).
|
||||
* RealGrid datetime은 Date 객체로 올 수 있어, 숫자만 이어붙이는 방식은 사용하지 않습니다.
|
||||
*/
|
||||
export function toCompactYmd(
|
||||
value: string | number | Date | null | undefined,
|
||||
): string {
|
||||
if (value == null || value === "") return "";
|
||||
|
||||
if (value instanceof Date) {
|
||||
return formatDateToCompactYmd(value);
|
||||
}
|
||||
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
if (value >= 19000101 && value <= 21001231) {
|
||||
const compact = String(Math.trunc(value)).padStart(8, "0").slice(-8);
|
||||
return isValidCompactYmdDigits(compact) ? compact : "";
|
||||
}
|
||||
if (value > 1e11) {
|
||||
return formatDateToCompactYmd(new Date(value));
|
||||
}
|
||||
}
|
||||
|
||||
const trimmed = String(value).trim();
|
||||
const isoLike = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
if (isoLike) {
|
||||
const compact = `${isoLike[1]}${isoLike[2]}${isoLike[3]}`;
|
||||
return isValidCompactYmdDigits(compact) ? compact : "";
|
||||
}
|
||||
|
||||
const digits = trimmed.replace(/\D/g, "");
|
||||
if (digits.length === 8 && isValidCompactYmdDigits(digits)) {
|
||||
return digits;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/** 설치목적 등 O/X 표기 → 대문자 O 또는 X (그 외는 원문 유지) */
|
||||
|
||||
@@ -2,30 +2,14 @@ import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type { RecentPostSearchParams } from "../model/post-interface";
|
||||
import type { UserPostSearchParams } from "../model/post-interface";
|
||||
|
||||
import { postApi } from "./post-service";
|
||||
|
||||
export function useRecentPostList(params?: RecentPostSearchParams) {
|
||||
export function useUserPostList(params?: UserPostSearchParams) {
|
||||
return useQuery({
|
||||
queryKey: createRecentPostListQueryKey(params),
|
||||
queryFn: () => postApi.getRecentPostList(params),
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, params],
|
||||
queryFn: () => postApi.getUserPostList(params),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
function createRecentPostListQueryKey(params?: RecentPostSearchParams) {
|
||||
return [
|
||||
ENDPOINT.BOARD_SERVICE.POSTS,
|
||||
"recentPosts",
|
||||
params?.page ?? "",
|
||||
params?.size ?? "",
|
||||
params?.sort ?? "",
|
||||
params?.keywordType ?? "",
|
||||
params?.keyword ?? "",
|
||||
params?.bbsId ?? "",
|
||||
params?.delYn ?? "",
|
||||
params?.frDateYmd ?? "",
|
||||
params?.toDateYmd ?? "",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { ENDPOINT, ST_DASHBOARD_BBS_ID } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
PostService,
|
||||
RecentPostListResponse,
|
||||
RecentPostSearchParams,
|
||||
UserPostListResponse,
|
||||
UserPostSearchParams,
|
||||
} from "../model/post-interface";
|
||||
|
||||
async function getRecentPostList(
|
||||
params?: RecentPostSearchParams,
|
||||
): Promise<RecentPostListResponse> {
|
||||
const response = await client.get<RecentPostListResponse>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/recentPosts`,
|
||||
async function getUserPostList(
|
||||
params?: UserPostSearchParams,
|
||||
): Promise<UserPostListResponse> {
|
||||
const response = await client.get<UserPostListResponse>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/user/${ST_DASHBOARD_BBS_ID}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const postApi: PostService = {
|
||||
getRecentPostList,
|
||||
getUserPostList,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
export { useRecentPostList } from "./api/post-queries";
|
||||
export { useUserPostList } from "./api/post-queries";
|
||||
export { postApi } from "./api/post-service";
|
||||
export {
|
||||
type PostService,
|
||||
type PstRecentListResponse,
|
||||
type RecentPostListResponse,
|
||||
type RecentPostSearchParams,
|
||||
} from "./model/post-interface";
|
||||
export { type PostService, type UserPost } from "./model/post-interface";
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { PageableList, PageableParams } from "@/shared/model";
|
||||
|
||||
export interface RecentPostSearchParams extends PageableParams {
|
||||
export interface UserPostSearchParams extends PageableParams {
|
||||
keywordType?: string;
|
||||
keyword?: string;
|
||||
bbsId?: number;
|
||||
delYn?: string;
|
||||
frDateYmd?: string;
|
||||
toDateYmd?: string;
|
||||
popupYn?: string;
|
||||
upndFixYn?: string;
|
||||
prdPstgYn?: string;
|
||||
siteId?: string;
|
||||
bbsTypeCd?: string;
|
||||
ansUseYn?: string;
|
||||
}
|
||||
|
||||
export interface PstRecentListResponse {
|
||||
export interface UserPost {
|
||||
rowNo: number;
|
||||
bbsId: number;
|
||||
pstNo: number;
|
||||
@@ -29,14 +31,12 @@ export interface PstRecentListResponse {
|
||||
regDt: string;
|
||||
rgtrNm: string;
|
||||
cmntCnt: number;
|
||||
bbsTypeCd: string;
|
||||
bbsNm: string;
|
||||
}
|
||||
|
||||
export type RecentPostListResponse = PageableList<PstRecentListResponse>;
|
||||
export type UserPostListResponse = PageableList<UserPost>;
|
||||
|
||||
export interface PostService {
|
||||
getRecentPostList(
|
||||
params?: RecentPostSearchParams,
|
||||
): Promise<RecentPostListResponse>;
|
||||
getUserPostList(
|
||||
params?: UserPostSearchParams,
|
||||
): Promise<UserPostListResponse>;
|
||||
}
|
||||
|
||||
+13
@@ -5,6 +5,8 @@ import {
|
||||
type StatisticsInputSewageReservoirPumpService,
|
||||
type SewageReservoirApiResponse,
|
||||
type SewageReservoirRequestParams,
|
||||
type SewageReservoirSaveRequest,
|
||||
type SewageReservoirSaveResponse,
|
||||
type RainfallPumpStationApiResponse,
|
||||
} from "../../model/input/statistics-input-sewage-reservoir-pump-interface";
|
||||
|
||||
@@ -28,6 +30,17 @@ export class StatisticsInputSewageReservoirPumpApiService implements StatisticsI
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 하수저류시설 저장 — ST00040001 */
|
||||
public async saveSewageReservoir(
|
||||
request: SewageReservoirSaveRequest,
|
||||
): Promise<SewageReservoirSaveResponse> {
|
||||
const response = await stClient.put<SewageReservoirSaveResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-reservoir-pump/sewage-reservoir/save`,
|
||||
request,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const statisticsInputSewageReservoirPumpApi = new StatisticsInputSewageReservoirPumpApiService();
|
||||
|
||||
@@ -293,7 +293,7 @@ export class StatisticsApiService implements StatisticsService {
|
||||
...(statsCd && statsCd !== "ALL" ? { statsCd } : {}),
|
||||
};
|
||||
const response = await stClient.get<VerificationResultListResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/results`,
|
||||
`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/results/input`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import { stClient } from "@/shared/api/client";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
VerificationInputReportRequest,
|
||||
VerificationInputReportResponse,
|
||||
VerificationInputReportService,
|
||||
} from "../model/verification-input-report-interface";
|
||||
|
||||
/** 이 API 전용 헤더 키 (백엔드 스펙: `token-id`) */
|
||||
const TOKEN_ID_HEADER = "token-id";
|
||||
|
||||
export class VerificationInputReportApiService implements VerificationInputReportService {
|
||||
public async getVerificationInputReport(
|
||||
param: VerificationInputReportRequest,
|
||||
): Promise<VerificationInputReportResponse> {
|
||||
const userInfo = await getUserInfo();
|
||||
const { statsCd, page, size, ...rest } = param;
|
||||
|
||||
const params: VerificationInputReportRequest = {
|
||||
...rest,
|
||||
...(statsCd && statsCd !== "ALL" ? { statsCd } : {}),
|
||||
...(page != null ? { page } : {}),
|
||||
...(size != null ? { size } : {}),
|
||||
};
|
||||
|
||||
const response = await stClient.get<VerificationInputReportResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/results/input/report`,
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
[TOKEN_ID_HEADER]: userInfo.userUnqId,
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const verificationInputReportApi = new VerificationInputReportApiService();
|
||||
+30
-1
@@ -28,7 +28,7 @@ export interface SewageReservoirCommonInfo {
|
||||
|
||||
/**
|
||||
* 하수저류시설 조회 `data.sewageReservoirList[]` 한 행.
|
||||
* `SewageReservoirInpColumn` fieldName과 동일 (ctpvNm은 commonInfo에서 그리드에 합침).
|
||||
* `SewageReservoirInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 `commonInfo.ctpvNm` 시도명).
|
||||
*/
|
||||
export interface SewageReservoirItem {
|
||||
admdstCd: string;
|
||||
@@ -166,9 +166,38 @@ export interface SewageReservoirRequestParams {
|
||||
fcltNm?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 하수저류시설(ST00040001) 저장 — `saveList` / `deleteList` 항목.
|
||||
* `sggNm`·검증결과는 저장 요청에서 제외.
|
||||
*/
|
||||
export type SewageReservoirSaveItem = Omit<
|
||||
SewageReservoirItem,
|
||||
"sggNm" | "verificationResult"
|
||||
>;
|
||||
|
||||
/** 하수저류시설(ST00040001) 저장 API 요청 */
|
||||
export interface SewageReservoirSaveRequest {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
sewerBzmnCd: string;
|
||||
userId: string;
|
||||
superAdmin?: string;
|
||||
noApplicable?: boolean;
|
||||
saveList: SewageReservoirSaveItem[];
|
||||
deleteList: SewageReservoirSaveItem[];
|
||||
}
|
||||
|
||||
export interface SewageReservoirSaveResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: string;
|
||||
}
|
||||
|
||||
export interface StatisticsInputSewageReservoirPumpService {
|
||||
getSewageReservoirList(params: SewageReservoirRequestParams): Promise<SewageReservoirApiResponse>;
|
||||
getRainfallPumpStationList(
|
||||
params: SewageReservoirRequestParams,
|
||||
): Promise<RainfallPumpStationApiResponse>;
|
||||
saveSewageReservoir(request: SewageReservoirSaveRequest): Promise<SewageReservoirSaveResponse>;
|
||||
}
|
||||
|
||||
@@ -159,10 +159,13 @@ export interface VerificationResultListRequest {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsCd?: string;
|
||||
/** 검증 방법 코드 (예: IMR) */
|
||||
/** 검증 방법 코드 (예: NV, IMR, HB) */
|
||||
vrfcMthdCd?: string;
|
||||
/** 처리여부 코드 목록, 콤마 구분 */
|
||||
/** 처리여부 코드 목록, 콤마 구분 (예: 0,2) */
|
||||
prcsYnList?: string;
|
||||
/** 반려상태 코드 목록, 콤마 구분 (예: 0,1,2) */
|
||||
rejectCodeList?: string;
|
||||
/** @deprecated 신규 API에서는 미사용 */
|
||||
pbcpAprvYn?: string;
|
||||
page?: number;
|
||||
size?: number;
|
||||
@@ -172,7 +175,6 @@ export interface VerificationResultListRequest {
|
||||
export interface VerificationResultReportItem {
|
||||
statsYr: string;
|
||||
statsCd: string;
|
||||
statsNm: string;
|
||||
sewerBzmnCd: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
@@ -181,18 +183,22 @@ export interface VerificationResultReportItem {
|
||||
vrfcMthdCd: string;
|
||||
vrfcMthdNm: string;
|
||||
vrfcTrgtNm: string;
|
||||
regDt: string;
|
||||
rsnExpln: string;
|
||||
nowVlCn: string;
|
||||
compVlCn: string | null;
|
||||
prcsYn: string;
|
||||
prcsYnNm: string;
|
||||
prcsOpnnExpln: string | null;
|
||||
prcrId: string | null;
|
||||
stVrfRjtComCd: string;
|
||||
stVrfRjtComNm: string;
|
||||
rjctOpnnCn: string | null;
|
||||
pbcpAprvYn: string;
|
||||
mdfrId: string | null;
|
||||
regDt: string;
|
||||
mdfcnDt: string;
|
||||
/** 하위 호환: 일부 API/화면에서만 내려올 수 있음 */
|
||||
statsNm?: string;
|
||||
prcrId?: string | null;
|
||||
pbcpAprvYn?: string;
|
||||
mdfrId?: string | null;
|
||||
}
|
||||
|
||||
export interface VerificationResultListPageInfo {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/** 이상치 선별 보고서 조회 요청 (`GET .../results/input/report`) */
|
||||
export interface VerificationInputReportRequest {
|
||||
statsYr: string;
|
||||
vrfcMthdCd: string;
|
||||
statsCd?: string;
|
||||
page?: number;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface VerificationInputReportTypeCountingInfo {
|
||||
nvCount: number;
|
||||
imrCount: number;
|
||||
hbCount: number;
|
||||
}
|
||||
|
||||
/** 이상치 선별 보고서 목록 한 행 */
|
||||
export interface VerificationInputReportItem {
|
||||
statsYr: string;
|
||||
statsCd: string;
|
||||
/** 통계코드명 (타이틀 표시용) */
|
||||
statsNm?: string;
|
||||
sewerBzmnCd: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string | null;
|
||||
vrfcSn: number;
|
||||
vrfcMthdCd: string;
|
||||
vrfcMthdNm: string;
|
||||
vrfcTrgtNm: string;
|
||||
rsnExpln: string;
|
||||
nowVlCn: string;
|
||||
compVlCn: string | null;
|
||||
vrfcStatusNm: string;
|
||||
}
|
||||
|
||||
export interface VerificationInputReportPageInfo {
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
currentPage: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface VerificationInputReportData {
|
||||
verifyTypeCountingInfo: VerificationInputReportTypeCountingInfo;
|
||||
verifyResultList: VerificationInputReportItem[];
|
||||
pageInfo: VerificationInputReportPageInfo;
|
||||
}
|
||||
|
||||
export interface VerificationInputReportResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: VerificationInputReportData;
|
||||
}
|
||||
|
||||
export interface VerificationInputReportService {
|
||||
getVerificationInputReport(
|
||||
param: VerificationInputReportRequest,
|
||||
): Promise<VerificationInputReportResponse>;
|
||||
}
|
||||
@@ -5,8 +5,9 @@ import { useRouter } from "next/navigation";
|
||||
import { createColumnHelper, getCoreRowModel } from "@tanstack/react-table";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { ST_DASHBOARD_BBS_ID } from "@/shared/config";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { type PstRecentListResponse, useRecentPostList } from "@/entities/post";
|
||||
import { type UserPost, useUserPostList } from "@/entities/post";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { useDragHandle } from "../sortable-panel";
|
||||
@@ -15,25 +16,23 @@ export function NoticePanel() {
|
||||
const { dragHandleProps, isDragging } = useDragHandle();
|
||||
const router = useRouter();
|
||||
|
||||
const recentPostListQuery = useRecentPostList({
|
||||
const userPostListQuery = useUserPostList({
|
||||
page: 0,
|
||||
size: 4,
|
||||
keywordType: "bbsTypeCd",
|
||||
keyword: "BBSNOTICE",
|
||||
});
|
||||
|
||||
const handleRowClick = (post: PstRecentListResponse) => {
|
||||
router.push(
|
||||
`/cms/${post.bbsTypeCd.toLowerCase()}/${post.bbsId}/${post.pstNo}`,
|
||||
);
|
||||
const handleRowClick = (post: UserPost) => {
|
||||
router.push(`/cms/bbsnotice/${ST_DASHBOARD_BBS_ID}/${post.pstNo}`);
|
||||
};
|
||||
|
||||
const handleMoreClick = () => {
|
||||
router.push("/cms/bbsnotice/7");
|
||||
router.push(`/cms/bbsnotice/${ST_DASHBOARD_BBS_ID}`);
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query: recentPostListQuery,
|
||||
query: userPostListQuery,
|
||||
tableOptions: {
|
||||
columns: noticeColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
@@ -52,8 +51,11 @@ export function NoticePanel() {
|
||||
title="헤더를 드래그하여 패널 순서 변경"
|
||||
>
|
||||
<h3 className="text-2xl font-semibold">공지사항</h3>
|
||||
</div>
|
||||
<div className="py-2 text-right">
|
||||
<Button
|
||||
size="sm"
|
||||
color="white"
|
||||
className="!text-xl"
|
||||
onClick={handleMoreClick}
|
||||
>
|
||||
@@ -65,7 +67,7 @@ export function NoticePanel() {
|
||||
);
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<PstRecentListResponse>();
|
||||
const columnHelper = createColumnHelper<UserPost>();
|
||||
|
||||
const noticeColumns = [
|
||||
columnHelper.accessor("pstTtl", {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Client } from "@common/network";
|
||||
|
||||
import { BASE_URL, ST_SERVICE_BASE_URL } from "../config/api";
|
||||
import { SITE_ID } from "../config/env";
|
||||
import { setCookie } from "../lib";
|
||||
@@ -6,11 +7,11 @@ import { setCookie } from "../lib";
|
||||
setCookie("site-id-key", SITE_ID);
|
||||
|
||||
export const client = Client.getInstance({
|
||||
baseURL: BASE_URL,
|
||||
siteId: SITE_ID,
|
||||
baseURL: BASE_URL,
|
||||
siteId: SITE_ID,
|
||||
});
|
||||
|
||||
export const stClient = Client.getInstance({
|
||||
baseURL: ST_SERVICE_BASE_URL,
|
||||
siteId: SITE_ID,
|
||||
});
|
||||
baseURL: ST_SERVICE_BASE_URL,
|
||||
siteId: SITE_ID,
|
||||
});
|
||||
|
||||
@@ -14,3 +14,4 @@ export const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
|
||||
export const ST_SERVICE_URL =
|
||||
process.env.NEXT_PUBLIC_ST_SERVICE_URL || "http://localhost:8080";
|
||||
export const COOKIE_DOMAIN = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;
|
||||
export const ST_DASHBOARD_BBS_ID = process.env.NEXT_PUBLIC_ST_DASHBOARD_BBS_ID;
|
||||
|
||||
Reference in New Issue
Block a user