From 8bfe1c9ab2825c1cad070e892b6da9ff7b4ca2b5 Mon Sep 17 00:00:00 2001 From: kiminchan Date: Mon, 24 Aug 2026 11:23:40 +0900 Subject: [PATCH 01/26] =?UTF-8?q?=EA=B0=95=EC=9A=B0=EA=B4=80=EC=B8=A1?= =?UTF-8?q?=EC=86=8C=20=EC=A7=80=EB=8F=84=20=ED=81=B4=EB=A6=AD=20=EB=B9=84?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/map/page.tsx | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 94da6bc7e..60b75ee9c 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -434,6 +434,7 @@ const DEFAULT_FEATURE_FIT_PADDING: [number, number, number, number] = [ 50, 50, 50, 50, ]; const SWAGE_PRCS_FCLT_LAYER_TYPE = "tb_il_swage_prcs_fclt"; +const RAIN_OBSERVATION_LAYER_TYPE = "tb_ms_rain_obsvtr_pstn_info"; type SwagePrcsFcltLayerMode = "all" | "gte500" | "lt500" | "none"; @@ -546,6 +547,21 @@ const normalizeFlowDirectionLayerInfos = (data: any[]): any[] => { }); }; +// 강우관측소는 위치와 라벨만 제공하며, 과거 DB 설정값이 Y여도 일반 속성 패널 선택 대상에서는 제외한다. +const normalizeRainObservationLayerInteraction = (data: any[]): any[] => { + if (!Array.isArray(data)) return data; + + return data.map((item) => { + const children = Array.isArray(item.children) + ? normalizeRainObservationLayerInteraction(item.children) + : item.children; + + return getLayerTypeName(item) === RAIN_OBSERVATION_LAYER_TYPE + ? { ...item, children, clickPsblYn: "N" } + : { ...item, children }; + }); +}; + // 레이어 설정의 최소 표출 줌을 읽되, 값이 없으면 유하방향 기본 줌 기준을 적용한다. const getFiniteNumber = (value: unknown) => { if (value === null || value === undefined || String(value).trim() === "") @@ -3793,7 +3809,11 @@ const Map = () => { appendExmnResultControlNodes( appendAgedPipeControlNode( appendSinkholeControlNode( - normalizeTreeRowSpacing(normalizeFlowDirectionLayerInfos(data)), + normalizeTreeRowSpacing( + normalizeRainObservationLayerInteraction( + normalizeFlowDirectionLayerInfos(data), + ), + ), ), ), exmnMapEntryContext.jobType, From 23c9ddf4ceb2ed2e73c6ee9d78a8ebe82576dd1c Mon Sep 17 00:00:00 2001 From: mind-one001 Date: Tue, 25 Aug 2026 15:29:55 +0900 Subject: [PATCH 02/26] =?UTF-8?q?ui=20=EB=B2=84=ED=8A=BC=20=EB=8F=99?= =?UTF-8?q?=EC=9E=91=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(with-base-layout)/ii/BaseLayout.tsx | 21 +- .../(with-base-layout)/oper/floodPre/page.tsx | 7 +- .../(with-base-layout)/oper/oldPp/page.tsx | 7 +- .../exmn/ui/ExmnBusinessRegisterPopupPage.tsx | 2 +- .../exmn/ui/ExmnPplUnitDetailTable.tsx | 66 +- .../features/exmn/ui/ExmnRegistrationPage.tsx | 2 +- .../exmn/ui/ExmnSurveyDetailPopupPage.tsx | 878 +++++++++--------- .../features/exmn/ui/TrtdstManagementPage.tsx | 4 +- .../windowPopup/model/popupDefinitions.ts | 2 +- 9 files changed, 493 insertions(+), 496 deletions(-) diff --git a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx index e984e6ce4..c4528ce94 100644 --- a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx +++ b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx @@ -109,6 +109,7 @@ const BaseLayout = ({ onDeleteFile, onAnalysis, onResultView, + selectedRowData, isIr = false, isRdii = false, }: BaseLayout) => { @@ -283,11 +284,7 @@ const BaseLayout = ({ )} {resultViewBtn && ( - )} @@ -295,6 +292,7 @@ const BaseLayout = ({ - )} + {graphBtn && } {/* 그래프 있을 시 */} {graphBtn && ( - )} diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx index 87cc92d8b..273453c0a 100644 --- a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx +++ b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx @@ -342,7 +342,12 @@ const Page = () => { - diff --git a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx index 40e707bac..8a8e3781f 100644 --- a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx +++ b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx @@ -317,7 +317,12 @@ const Page = () => { - diff --git a/apps/sn/src/features/exmn/ui/ExmnBusinessRegisterPopupPage.tsx b/apps/sn/src/features/exmn/ui/ExmnBusinessRegisterPopupPage.tsx index 8ebc6c279..c6aa579a8 100644 --- a/apps/sn/src/features/exmn/ui/ExmnBusinessRegisterPopupPage.tsx +++ b/apps/sn/src/features/exmn/ui/ExmnBusinessRegisterPopupPage.tsx @@ -438,7 +438,7 @@ export function ExmnBusinessRegisterPopupPage({ disabled={isLoading || formLocked} onClick={() => void handleSave()} > - {isEditMode ? "수정" : "저장"} + 저장 - + sum + width, 0); return ( -
- - - {COLUMN_WIDTHS.map((width, index) => ( - - ))} - - - - - - - - - - - - - - - - - - - - - - - - - {rows.length > 0 ? ( - rows.map((row, index) => { - const pipeSpan = pipeSpans.get(row.pipeNo); - const unitKey = `${row.pipeNo}::${row.unitSeq}`; - const unitSpan = unitSpans.get(unitKey); - const showPipeCell = pipeSpan?.firstIndex === index; - const showUnitCells = unitSpan?.firstIndex === index; +
- 관로번호 - - 단위 -
- 구간 -
- 단위 -
- 길이 -
- (m) -
- 결함 -
- 위치 -
- (m) -
- 결함 - - 등급점수(점) - - 조사중단사유 - - 비고 -
- 종류 - - 코드 - - 등급 - - 발생 -
- 위치 -
- 침입수 -
- 가점 -
- 연속 -
- 결함 -
- 구조적 - - 운영적 -
+ + {COLUMN_WIDTHS.map((width, index) => ( + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + {rows.length > 0 ? ( + rows.map((row, index) => { + const pipeSpan = pipeSpans.get(row.pipeNo); + const unitKey = `${row.pipeNo}::${row.unitSeq}`; + const unitSpan = unitSpans.get(unitKey); + const showPipeCell = pipeSpan?.firstIndex === index; + const showUnitCells = unitSpan?.firstIndex === index; - return ( - - {showPipeCell && ( - - )} - {showUnitCells && ( - <> - - - - )} - - - - - - - - - - - - - ); - }) - ) : ( - - - - )} - -
+ 관로번호 + + 단위 +
+ 구간 +
+ 단위 +
+ 길이 +
+ (m) +
+ 결함 +
+ 위치 +
+ (m) +
+ 결함 + + 등급점수(점) + + 조사중단사유 + + 비고 +
+ 종류 + + 코드 + + 등급 + + 발생 +
+ 위치 +
+ 침입수 +
+ 가점 +
+ 연속 +
+ 결함 +
+ 구조적 + + 운영적 +
- {row.pipeNo} - - {row.unitSeq} - - {row.unitLengthM} - - {formatOptionalValue(row.defectPositionM)} - - {row.defectName} - - {row.code} - - {row.severity ? SEVERITY_LABELS[row.severity] : "-"} - - {formatOptionalValue(row.occurrencePosition)} - - {formatOptionalValue(row.infiltrationBonus)} - - {row.continuous == null ? "-" : row.continuous ? "Y" : "N"} - - {formatOptionalValue(row.structuralScore)} - - {formatOptionalValue(row.operationalScore)} - - {formatOptionalValue(row.surveyStopReason)} - - {formatOptionalValue(row.remark)} -
- {emptyMessage} -
-
+ {showPipeCell && ( + + {row.pipeNo} + + )} + {showUnitCells && ( + <> + + {row.unitSeq} + + + {row.unitLengthM} + + + )} + + {formatOptionalValue(row.defectPositionM)} + + + {row.defectName} + + + {row.code} + + + {row.severity ? SEVERITY_LABELS[row.severity] : "-"} + + + {formatOptionalValue(row.occurrencePosition)} + + + {formatOptionalValue(row.infiltrationBonus)} + + + {row.continuous == null ? "-" : row.continuous ? "Y" : "N"} + + + {formatOptionalValue(row.structuralScore)} + + + {formatOptionalValue(row.operationalScore)} + + + {formatOptionalValue(row.surveyStopReason)} + + + {formatOptionalValue(row.remark)} + + + ); + }) + ) : ( + + + {emptyMessage} + + + )} + + ); } diff --git a/apps/sn/src/features/exmn/ui/ExmnSurveyDetailPopupPage.tsx b/apps/sn/src/features/exmn/ui/ExmnSurveyDetailPopupPage.tsx index a0851ceb4..cf13ff90e 100644 --- a/apps/sn/src/features/exmn/ui/ExmnSurveyDetailPopupPage.tsx +++ b/apps/sn/src/features/exmn/ui/ExmnSurveyDetailPopupPage.tsx @@ -13,9 +13,9 @@ import { SelectItem, SelectTrigger, SelectValue, + Table, } from "@common/components"; import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel"; -import LoadingOverlay from "@/components/ui/LoadingBar"; import { getExmnFacilityDetails, @@ -40,6 +40,7 @@ import { } from "../model/exmn-detail-table"; import { ExmnPplUnitDetailTable } from "./ExmnPplUnitDetailTable"; +import { ExmnSurveyStandardDataTable } from "./ExmnSurveyStandardDataTable"; const SEVERITY_LABELS: Record = { L: "대", @@ -705,6 +706,9 @@ export function ExmnSurveyDetailPopupPage({ () => new Set(unitRows.map((row) => `${row.pipeNo}::${row.unitSeq}`)).size, [unitRows], ); + /** 관로 집계와 본단위구간 행을 같은 StandardDataTable 데이터 유무 계약으로 전달한다. */ + const standardTableRows: Array = + isUnitMode ? unitRows : rows; const popupTitle = isUnitMode ? "관로조사 본단위구간 상세현황" : defectType === "ALL" @@ -735,7 +739,6 @@ export function ExmnSurveyDetailPopupPage({ } bodyClassName="overflow-x-hidden" > - {notice && (
)} -
-
-

{tableTitle}

+ {isUnitMode ? ( <> @@ -898,10 +912,9 @@ export function ExmnSurveyDetailPopupPage({ )}
-
- -
- {isUnitMode ? ( + } + renderTable={() => + isUnitMode ? ( ) : ( -
- - - {fixedColumns.map((column) => ( - - ))} - {Array.from({ - length: + + {fixedColumns.map((column) => ( + + ))} + {Array.from({ + length: + (visibleDefectColumns.length + 1) * + EXMN_DETAIL_SEVERITIES.length, + }).map((_, index) => ( + + ))} + + + + {fixedColumns.map((column, columnIndex) => { + const sticky = columnIndex < STICKY_COLUMN_COUNT; + const columnLabel = column.label; + return ( + + ); + })} + - ))} - - - - {fixedColumns.map((column, columnIndex) => { - const sticky = columnIndex < STICKY_COLUMN_COUNT; - const columnLabel = column.label; - return ( - - ); - })} - - - + EXMN_DETAIL_SEVERITIES.length + } + className="border-b border-r border-border !py-[0.8rem] px-2 text-center" + > + 이상항목 + + + + + {visibleDefectColumns.map((column) => ( + ))} + + + {EXMN_DETAIL_SEVERITIES.map((severity) => ( + - {visibleDefectColumns.map((column) => ( + ))} + {visibleDefectColumns.flatMap((column) => + EXMN_DETAIL_SEVERITIES.map((severity) => ( - ))} - - - {EXMN_DETAIL_SEVERITIES.map((severity) => ( - - ))} - {visibleDefectColumns.flatMap((column) => - EXMN_DETAIL_SEVERITIES.map((severity) => ( - + + {rows.length > 0 ? ( + <> + {rows.map((row) => { + const visibleAnomalyTotals = getVisibleAnomalyTotals( + row, + visibleDefectColumns, + ); + return ( + - {SEVERITY_LABELS[severity]} - - )), - )} - - - - {rows.length > 0 ? ( - <> - {rows.map((row) => { - const visibleAnomalyTotals = getVisibleAnomalyTotals( - row, - visibleDefectColumns, - ); - return ( - - {fixedColumns.map((column, columnIndex) => { - const sticky = - columnIndex < STICKY_COLUMN_COUNT; - return ( - - ); - })} - {EXMN_DETAIL_SEVERITIES.map((severity) => ( + {fixedColumns.map((column, columnIndex) => { + const sticky = columnIndex < STICKY_COLUMN_COUNT; + return ( - ))} - {visibleDefectColumns.flatMap((column) => - EXMN_DETAIL_SEVERITIES.map((severity) => ( - - )), - )} - - ); - })} - - + ))} + {visibleDefectColumns.flatMap((column) => + EXMN_DETAIL_SEVERITIES.map((severity) => ( + + )), + )} + + ); + })} + + + {summaryLabelColumnCount > STICKY_COLUMN_COUNT && ( + - {summaryLabelColumnCount > STICKY_COLUMN_COUNT && ( + ))} + {EXMN_DETAIL_SEVERITIES.map((severity) => ( + + ))} + {visibleDefectColumns.flatMap((column) => + EXMN_DETAIL_SEVERITIES.map((severity) => ( - ))} - {EXMN_DETAIL_SEVERITIES.map((severity) => ( - - ))} - {visibleDefectColumns.flatMap((column) => - EXMN_DETAIL_SEVERITIES.map((severity) => ( - - )), - )} - - - + - {summaryLabelColumnCount > STICKY_COLUMN_COUNT && ( - - ))} + 합계 + + + {summaryLabelColumnCount > STICKY_COLUMN_COUNT && ( + ))} + + {visibleDefectColumns.map((column) => ( + - {visibleDefectColumns.map((column) => ( - - ))} - - - ) : ( - - + ))} - )} - -
+ + {columnLabel} + + ( -
- - {columnLabel} - - - 이상항목 -
+ 합계 + + {column.label} ({column.code}) +
- 합계 + {SEVERITY_LABELS[severity]} - {column.label} ({column.code}) -
{SEVERITY_LABELS[severity]} +
- {getFixedCellValue(row, column.id)} - - {formatCount(visibleAnomalyTotals[severity])} + {getFixedCellValue(row, column.id)} - {formatCount( - row.defectCounts[column.code]?.[severity], - )} -
( + + {formatCount(visibleAnomalyTotals[severity])} + + {formatCount( + row.defectCounts[column.code]?.[severity], + )} +
+ - - 소계 - + 소계 + + + {summary.value} + {formatCount( + defectSummary.anomalySubtotals[severity], + )} + - {summary.value} - {formatCount( - defectSummary.anomalySubtotals[severity], + defectSummary.defectSubtotals[column.code]?.[ + severity + ], )} - {formatCount( - defectSummary.defectSubtotals[column.code]?.[ - severity - ], - )} -
+
+ - - 합계 - - - {summary.value} - + {summary.value} + + {formatCount(defectSummary.anomalyTotal)} + - {formatCount(defectSummary.anomalyTotal)} + {formatCount( + defectSummary.defectTotals[column.code], + )} - {formatCount( - defectSummary.defectTotals[column.code], - )} -
- 조회된 상세 결과가 없습니다. -
-
- )} -
-
+ + ) : ( + + + 조회된 상세 결과가 없습니다. + + + )} + + + ) + } + /> ); diff --git a/apps/sn/src/features/exmn/ui/ExmnSurveyStandardDataTable.tsx b/apps/sn/src/features/exmn/ui/ExmnSurveyStandardDataTable.tsx new file mode 100644 index 000000000..fd498371e --- /dev/null +++ b/apps/sn/src/features/exmn/ui/ExmnSurveyStandardDataTable.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { useMemo, type ReactNode, type UIEventHandler } from "react"; +import { + getCoreRowModel, + useReactTable, + type ColumnDef, +} from "@tanstack/react-table"; + +import { StandardDataTable } from "@common/business_components"; + +interface ExmnSurveyStandardDataTableProps { + rows: T[]; + title: ReactNode; + actions?: ReactNode; + loading?: boolean; + emptyMessage: string; + minTableWidth?: string; + onScroll?: UIEventHandler; + renderTable: () => ReactNode; +} + +/** + * 조사 복합표의 병합 셀은 업무 화면에서 유지하고 카드·헤더·로딩·빈 상태·스크롤은 + * StandardDataTable이 소유하도록 연결한다. + */ +export function ExmnSurveyStandardDataTable({ + rows, + title, + actions, + loading = false, + emptyMessage, + minTableWidth = "100%", + onScroll, + renderTable, +}: ExmnSurveyStandardDataTableProps) { + /** 공통 표가 데이터 유무와 로딩 상태를 판단할 수 있도록 복합 행을 최소 열로 연결한다. */ + const columns = useMemo[]>( + () => [ + { + id: "complexRow", + accessorFn: (_row, index) => index, + }, + ], + [], + ); + const table = useReactTable({ + data: rows, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( + + ); +} + +export type { ExmnSurveyStandardDataTableProps }; diff --git a/apps/sn/src/features/windowPopup/ui/PopupSearchPanel.tsx b/apps/sn/src/features/windowPopup/ui/PopupSearchPanel.tsx index e7d4f871a..8ccabac19 100644 --- a/apps/sn/src/features/windowPopup/ui/PopupSearchPanel.tsx +++ b/apps/sn/src/features/windowPopup/ui/PopupSearchPanel.tsx @@ -10,6 +10,18 @@ type PopupSearchPanelProps = { contentClassName?: string; }; +type PopupSearchFieldProps = { + label: ReactNode; + children: ReactNode; + className?: string; + labelClassName?: string; +}; + +type PopupSearchActionsProps = { + children: ReactNode; + className?: string; +}; + /** WindowPopup 조회조건의 외곽 카드, 패딩과 가로 스크롤 규칙을 한 곳에서 관리한다. */ export default function PopupSearchPanel({ children, @@ -24,9 +36,59 @@ export default function PopupSearchPanel({ className, )} > -
{children}
+
+ {children} +
); } -export type { PopupSearchPanelProps }; +/** 조회조건 라벨과 입력 컨트롤을 같은 행에 배치한다. */ +export function PopupSearchField({ + label, + children, + className, + labelClassName, +}: PopupSearchFieldProps) { + return ( + + ); +} + +/** 조회·초기화 버튼을 검색조건의 우측 끝에 같은 간격으로 배치한다. */ +export function PopupSearchActions({ + children, + className, +}: PopupSearchActionsProps) { + return ( +
+ {children} +
+ ); +} + +export type { + PopupSearchActionsProps, + PopupSearchFieldProps, + PopupSearchPanelProps, +}; diff --git a/packages/common/business_components/src/ui/standard_data_table/index.tsx b/packages/common/business_components/src/ui/standard_data_table/index.tsx index a6c1e0ab2..0a83e5ce6 100644 --- a/packages/common/business_components/src/ui/standard_data_table/index.tsx +++ b/packages/common/business_components/src/ui/standard_data_table/index.tsx @@ -8,6 +8,7 @@ import { useState, type CSSProperties, type ReactNode, + type UIEventHandler, } from "react"; import type { Table as TableInstance } from "@tanstack/react-table"; import { ChevronLeft, ChevronRight } from "lucide-react"; @@ -21,6 +22,10 @@ import { type ScrollMode = "none" | "horizontal" | "vertical" | "both"; +type StandardDataTableRenderContext = { + table: TableInstance; +}; + type StandardDataTableProps = { table: TableInstance; title?: ReactNode; @@ -38,6 +43,10 @@ type StandardDataTableProps = { flushOuterMargin?: boolean; /** 빈 목록일 때 부모의 남은 높이를 채워 팝업·페이지의 과도한 바깥 여백을 줄인다. */ fillAvailableHeight?: boolean; + /** 병합 셀·고정 열이 필요한 복합표가 공통 카드·로딩·스크롤 안에서 표 본문만 교체한다. */ + renderTable?: (context: StandardDataTableRenderContext) => ReactNode; + /** 고정 열 경계처럼 가로 스크롤 위치가 필요한 소비 화면에 공통 스크롤 이벤트를 전달한다. */ + onScroll?: UIEventHandler; onRowClick?: (row: T) => void; }; @@ -185,6 +194,8 @@ function StandardDataTable({ viewPagination = true, flushOuterMargin = false, fillAvailableHeight = false, + renderTable, + onScroll, onRowClick, }: StandardDataTableProps) { const rowCount = table.getRowModel().rows.length; @@ -272,6 +283,7 @@ function StandardDataTable({ >
({ )} > {loading ? ( - +
+ +
) : ( emptyMessage )}
) : ( - + (renderTable?.({ table }) ?? ( + + )) )} {viewPagination && !loading && rowCount > 0 ? ( @@ -313,4 +333,9 @@ function StandardDataTable({ } export { STANDARD_ROW_NUMBER_HEADER, StandardDataTable, getStandardRowNumber }; -export type { ScrollMode, StandardDataTableProps, StandardRowNumberParams }; +export type { + ScrollMode, + StandardDataTableProps, + StandardDataTableRenderContext, + StandardRowNumberParams, +}; From 42f83bcd2471755ad7245d50bf9e036f205c1d0b Mon Sep 17 00:00:00 2001 From: ljpark Date: Wed, 26 Aug 2026 10:06:55 +0900 Subject: [PATCH 04/26] =?UTF-8?q?sn:=20=EC=B9=A8=EC=88=98=EC=98=88?= =?UTF-8?q?=EC=B8=A1=ED=98=84=ED=99=A9=20=EB=AA=A9=EB=A1=9D=20=ED=95=AD?= =?UTF-8?q?=EB=AA=A9=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/FldngPredcPrstPopupContent.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/sn/src/features/fldngPredcPrst/ui/FldngPredcPrstPopupContent.tsx b/apps/sn/src/features/fldngPredcPrst/ui/FldngPredcPrstPopupContent.tsx index a1c782952..2635ab52c 100644 --- a/apps/sn/src/features/fldngPredcPrst/ui/FldngPredcPrstPopupContent.tsx +++ b/apps/sn/src/features/fldngPredcPrst/ui/FldngPredcPrstPopupContent.tsx @@ -57,18 +57,26 @@ const PAGE_SIZE = 10; const MAP_FREQUENCIES = new Set([30, 50, 80, 100]); const FREQUENCY_OPTIONS = [30, 50, 80, 100] as const; +/** 도시침수지도 DB의 3자리 빈도 코드로 변환한다. 지도는 100년까지만 제공하므로 그 이상은 100년으로 제한한다. */ +const toFloodMapFrequencyCode = (value: number) => { + if (!Number.isFinite(value) || value <= 0) return null; + const normalized = value > 100 ? 100 : value; + if (!MAP_FREQUENCIES.has(normalized)) return null; + return String(normalized).padStart(3, "0"); +}; + const HEADS: UiHeadProps[] = [ { id: "rowNo", title: "순번", widthPercent: 6 }, { id: "ctpvNm", title: "시도", widthPercent: 8 }, { id: "sggNm", title: "시군구", widthPercent: 9 }, { - id: "rainBeginningDateTimeDisplay", - title: "강우시각", + id: "analysisDateTimeDisplay", + title: "분석시각", widthPercent: 13, }, { - id: "analysisDateTimeDisplay", - title: "분석시각", + id: "rainBeginningDateTimeDisplay", + title: "강우시각", widthPercent: 13, }, { @@ -297,7 +305,8 @@ export default function FldngPredcPrstPopupContent() { /** 선택 행과 동일한 시군구·빈도의 도시침수지도 범위를 부모 지도에 전달한다. */ const handleRowClick = async (row: TableRow) => { const frequencyYear = Number(row.exceededFrequencyYear ?? 0); - if (!MAP_FREQUENCIES.has(frequencyYear)) { + const sbmrgFrqExpln = toFloodMapFrequencyCode(frequencyYear); + if (!sbmrgFrqExpln) { alert( `${frequencyYear || "해당"}년 빈도 도시침수지도는 제공되지 않습니다. (30·50·80·100년 빈도만 제공)`, ); @@ -315,7 +324,6 @@ export default function FldngPredcPrstPopupContent() { setSelectedRowId(buildRowId(row)); setLoading(true); try { - const sbmrgFrqExpln = `${frequencyYear}년`; const params = new URLSearchParams({ sggCd: row.sggCd, sbmrgFrqExpln, @@ -471,8 +479,8 @@ export default function FldngPredcPrstPopupContent() { - {renderDateTimeRange("강우시각", "rainBgngFrom", "rainBgngTo")} {renderDateTimeRange("분석시각", "analysisFrom", "analysisTo")} + {renderDateTimeRange("강우시각", "rainBgngFrom", "rainBgngTo")}