diff --git a/apps/sn/app/(map-layout)/map/components/waterRisk/WaterRiskPopupPage.tsx b/apps/sn/app/(map-layout)/map/components/waterRisk/WaterRiskPopupPage.tsx index 75c059648..fa2e8cf84 100644 --- a/apps/sn/app/(map-layout)/map/components/waterRisk/WaterRiskPopupPage.tsx +++ b/apps/sn/app/(map-layout)/map/components/waterRisk/WaterRiskPopupPage.tsx @@ -1,22 +1,21 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import { - StandardPopupLayout, - UITab, - type UiHeadProps, -} from "@common/business_components"; -import { Button } from "@common/components"; -import LoadingOverlay from "@/components/ui/LoadingBar"; import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions"; -import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable"; -import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel"; - import { WINDOW_POPUP_MESSAGE_TYPE, type WindowPopupContentProps, } from "@/features/windowPopup/model/types"; +import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable"; +import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel"; +import { + StandardPopupLayout, + type UiHeadProps, + UITab, +} from "@common/business_components"; +import type { UiTableSort } from "@common/business_components/ui/table"; +import { Button } from "@common/components"; import { fetchWaterRiskAreas, @@ -32,6 +31,8 @@ import { type WaterRiskSessionSnapshot, } from "./waterRiskTypes"; +import LoadingOverlay from "@/components/ui/LoadingBar"; + type WaterRiskResultMode = "current" | "history"; type ResultFilters = { @@ -63,38 +64,59 @@ type WaterRiskTableRow = Record & { currentAccumRainQty: string; forecastRainQty: string; maxFloodingAt: string; - maxFloodingVolume: string; + maxFloodingFlow: string; riskStatus: WaterRiskResultRow["riskStatus"]; }; const WATER_RISK_RESULT_HEADLIST: UiHeadProps[] = [ - { id: "rowNo", title: "순번", widthPercent: 6 }, - { id: "sidoName", title: "시도", widthPercent: 8 }, - { id: "sggName", title: "시군구", widthPercent: 8 }, - { id: "areaName", title: "중점관리지역", widthPercent: 14 }, - { id: "rainStartedAt", title: "강우시작시간", widthPercent: 13 }, + { + id: "rowNo", + title: "순번", + widthPercent: 6, + sortable: true, + sortKey: "rowNumber", + }, + { + id: "sidoName", + title: "시도", + widthPercent: 8, + sortable: true, + sortKey: "ctpvName", + }, + { id: "sggName", title: "시군구", widthPercent: 8, sortable: true }, + { id: "areaName", title: "중점관리지역", widthPercent: 14, sortable: true }, + { + id: "rainStartedAt", + title: "강우시작시간", + widthPercent: 13, + sortable: true, + }, { id: "currentAccumRainQty", title: "누적강우량(mm)", widthPercent: 11, align: "right", + sortable: true, }, { id: "forecastRainQty", title: "예측강우량(mm)", widthPercent: 11, align: "right", + sortable: true, }, { id: "maxFloodingAt", title: "최대 침수 예상시점", widthPercent: 13, + sortable: true, }, { - id: "maxFloodingVolume", - title: "최대침수량(m³)", + id: "maxFloodingFlow", + title: "최대 침수유량(m³/s)", widthPercent: 10, align: "right", + sortable: true, }, { id: "riskStatus", title: "위험 여부", widthPercent: 7 }, ]; @@ -169,13 +191,13 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { useState(EMPTY_RESULT_PAGE); const [selectedResultKey, setSelectedResultKey] = useState(""); const [resultCurrentPage, setResultCurrentPage] = useState(1); + const [resultSort, setResultSort] = useState(); const [reloadVersion, setReloadVersion] = useState(0); const [currentAnalysisBaseAt, setCurrentAnalysisBaseAt] = useState(() => formatCurrentAnalysisHour(new Date()), ); const [isLoading, setIsLoading] = useState(false); const [isMapLoading, setIsMapLoading] = useState(false); - const [noticeMessage, setNoticeMessage] = useState(""); const [errorMessage, setErrorMessage] = useState(""); useEffect(() => { @@ -225,6 +247,7 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { rainEventStatus: resultMode === "current" ? "IN_PROGRESS" : "ENDED", page: resultCurrentPage - 1, size: WATER_RISK_PAGE_SIZE, + sort: resultSort, }) .then((page) => { if (!active) return; @@ -256,6 +279,7 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { reloadVersion, resultCurrentPage, resultMode, + resultSort, ]); useEffect(() => { @@ -281,6 +305,19 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { }; }, [resultPage.items]); + /** 브라우저 X·새로고침 등으로 팝업 문서가 종료되면 부모 지도의 선택결과를 즉시 정리한다. */ + useEffect(() => { + const notifyPopupClosed = () => { + postMessageToOpener(WINDOW_POPUP_MESSAGE_TYPE.WATER_RISK_CLOSED); + }; + window.addEventListener("pagehide", notifyPopupClosed); + window.addEventListener("beforeunload", notifyPopupClosed); + return () => { + window.removeEventListener("pagehide", notifyPopupClosed); + window.removeEventListener("beforeunload", notifyPopupClosed); + }; + }, []); + /** 현재 탭의 표시·조회 기준시간을 매 정시에 새 현재시간으로 변경한다. */ useEffect(() => { if (resultMode !== "current" || typeof window === "undefined") return; @@ -363,8 +400,7 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { currentAccumRainQty: formatOptionalNumber(row.currentAccumRainQty, 1), forecastRainQty: formatOptionalNumber(row.forecastRainQty, 1), maxFloodingAt: formatAnalysisBaseAt(row.maxFloodingAt), - // 목록 API에는 최대 침수유량만 있고 침수량 합계는 아직 제공하지 않는다. - maxFloodingVolume: "-", + maxFloodingFlow: formatOptionalNumber(row.maxFloodingFlow, 2), riskStatus: row.riskStatus, }; }), @@ -374,18 +410,23 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { /** 검증된 검색조건을 적용하고 첫 페이지를 새로 조회한다. */ const handleApplyResultFilters = () => { if (!draftResultFilters.startDate || !draftResultFilters.endDate) { - setNoticeMessage("조회 시작일과 종료일을 입력해야 합니다."); + setErrorMessage("조회 시작일과 종료일을 입력해야 합니다."); return; } if (draftResultFilters.startDate > draftResultFilters.endDate) { - setNoticeMessage("조회 시작일은 종료일보다 늦을 수 없습니다."); + setErrorMessage("조회 시작일은 종료일보다 늦을 수 없습니다."); return; } setResultCurrentPage(1); setAppliedResultFilters({ ...draftResultFilters }); setReloadVersion((version) => version + 1); - setNoticeMessage("통수위험 분석 결과를 조회했습니다."); + }; + + /** 공통 결과표 정렬상태를 적용하고 첫 페이지를 다시 조회한다. */ + const handleSortChange = (nextSort?: UiTableSort) => { + setResultCurrentPage(1); + setResultSort(nextSort); }; /** 강우이벤트 상태에 따라 진행중 현재 위험과 종료된 과거 이력을 다시 조회한다. */ @@ -394,7 +435,6 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { setResultCurrentPage(1); setResultPage(EMPTY_RESULT_PAGE); setSelectedResultKey(""); - setNoticeMessage(""); }; /** 선택 행의 실제 GeoJSON을 조회한 뒤 부모 지도 창에 전달한다. */ @@ -409,7 +449,6 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { resultRow: row, mapView, }); - setNoticeMessage(`${row.areaName}을 지도에서 강조했습니다.`); } catch (error) { setErrorMessage( error instanceof Error @@ -436,8 +475,8 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { variant="underline" defaultValue="current" tabList={[ - { value: "current", label: "현재 위험" }, - { value: "history", label: "과거 이력" }, + { value: "current", label: "현재" }, + { value: "history", label: "과거분석이력" }, ]} onValueChange={(value) => handleChangeResultMode(value as WaterRiskResultMode) @@ -458,7 +497,7 @@ export default function WaterRiskPopupPage(_props: WindowPopupContentProps) { <>