diff --git a/apps/sn/app/(map-layout)/map/components/analysis-menu/analysis-popup-menu.tsx b/apps/sn/app/(map-layout)/map/components/analysis-menu/analysis-popup-menu.tsx index 182ae535a..6b295d10c 100644 --- a/apps/sn/app/(map-layout)/map/components/analysis-menu/analysis-popup-menu.tsx +++ b/apps/sn/app/(map-layout)/map/components/analysis-menu/analysis-popup-menu.tsx @@ -10,9 +10,7 @@ import { ChartColumnBig, ChevronUp, PanelsTopLeft, - PencilRuler, Search, - Settings2, TriangleAlert, Waves, } from "lucide-react"; @@ -28,8 +26,6 @@ import { getRawJson } from "@/shared/api/sewerage-api"; type Props = { onOpenPopup: (popupKey: WindowPopupKey) => Window | null; onOpenInfoSearch: () => void; - onToggleGisEdit?: () => void; - isGisEditActive?: boolean; isInfoSearchActive?: boolean; initialActiveKey?: WindowPopupKey | null; }; @@ -89,16 +85,6 @@ const ANALYSIS_POPUP_ITEMS: Array<{ label: windowPopupDefinitions.agedPipeStatusByCtpvPopup.title, icon: ChartColumnBig, }, - { - key: "gisEditPopup", - label: windowPopupDefinitions.gisEditPopup.title, - icon: PencilRuler, - }, - { - key: "gisManagementPopup", - label: windowPopupDefinitions.gisManagementPopup.title, - icon: Settings2, - }, { key: "flowRiskPopup", label: windowPopupDefinitions.flowRiskPopup.title, @@ -119,8 +105,6 @@ const ANALYSIS_POPUP_ITEMS: Array<{ export default function AnalysisPopupMenu({ onOpenPopup, onOpenInfoSearch, - onToggleGisEdit, - isGisEditActive = false, isInfoSearchActive = false, initialActiveKey = null, }: Props) { @@ -155,12 +139,6 @@ export default function AnalysisPopupMenu({ }; }, []); - useEffect(() => { - if (!isGisEditActive && activeKey === "gisEditPopup") { - setActiveKey(null); - } - }, [activeKey, isGisEditActive]); - useEffect(() => { if (!isInfoSearchActive && activeKey === "infoSearch") { setActiveKey(null); @@ -214,10 +192,7 @@ export default function AnalysisPopupMenu({
{ANALYSIS_POPUP_ITEMS.map((item) => { const Icon = item.icon; - const isActive = - item.key === "gisEditPopup" - ? isGisEditActive - : activeKey === item.key; + const isActive = activeKey === item.key; const label = item.key === "fldngPredcPrst" ? `침수예측현황(${seriousPredictionCount}건)` @@ -233,12 +208,6 @@ export default function AnalysisPopupMenu({ : "border-transparent bg-background text-foreground hover:border-border hover:bg-accent hover:text-primary" }`} onClick={() => { - if (item.key === "gisEditPopup" && onToggleGisEdit) { - setActiveKey(item.key); - onToggleGisEdit(); - return; - } - setActiveKey(item.key); if (item.key === "infoSearch") { diff --git a/apps/sn/app/(map-layout)/map/components/gis-edit/use-gis-edit.ts b/apps/sn/app/(map-layout)/map/components/gis-edit/use-gis-edit.ts index f37e91f78..403ddbaf4 100644 --- a/apps/sn/app/(map-layout)/map/components/gis-edit/use-gis-edit.ts +++ b/apps/sn/app/(map-layout)/map/components/gis-edit/use-gis-edit.ts @@ -34,7 +34,7 @@ import type { export const useGisEdit = (treeData: any[]) => { const editableLayers = useMemo(() => collectEditableLayers(treeData), [treeData]); - // GIS 편집 모드 on/off는 우측 분석 메뉴의 GIS 편집 버튼과 직접 연결된다. + // GIS 편집 모드 on/off는 지도 우측 GIS 편집 아이콘과 직접 연결된다. const [isEditMode, setIsEditMode] = useState(false); // 현재 선택 레이어를 기준으로 허용 도구와 저장 payload의 layerId를 결정한다. const [selectedLayerId, setSelectedLayerId] = useState("tb_ms_swage_ppl"); diff --git a/apps/sn/app/(map-layout)/map/components/gis-tools/gis-tool-buttons.tsx b/apps/sn/app/(map-layout)/map/components/gis-tools/gis-tool-buttons.tsx new file mode 100644 index 000000000..846de173a --- /dev/null +++ b/apps/sn/app/(map-layout)/map/components/gis-tools/gis-tool-buttons.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { PencilRuler, Settings2 } from "lucide-react"; + +type Props = { + isGisEditActive: boolean; + onToggleGisEdit: () => void; + onOpenGisManagement: () => Window | null; +}; + +/** 기존 지도 도구와 같은 크기로 GIS 버튼의 기본·활성·키보드 포커스를 표시한다. */ +const getButtonClass = (isActive: boolean) => + `flex h-[40px] w-[40px] items-center justify-center p-0 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary ${ + isActive + ? "bg-primary text-primary-foreground" + : "bg-background text-label hover:bg-primary hover:text-primary-foreground" + }`; + +/** 분석도구 메뉴와 독립적으로 GIS 편집 모드와 관리 팝업의 진입 버튼을 제공한다. */ +export default function GisToolButtons({ + isGisEditActive, + onToggleGisEdit, + onOpenGisManagement, +}: Props) { + // 관리 팝업이 닫히면 버튼의 활성 표시도 해제한다. + const [isGisManagementActive, setIsGisManagementActive] = useState(false); + // 렌더링과 무관한 감지 타이머를 보관해 재클릭·화면 이탈 시 중복 실행을 방지한다. + const closeWatcherRef = useRef(null); + + useEffect(() => () => { + if (closeWatcherRef.current !== null) { + window.clearInterval(closeWatcherRef.current); + } + }, []); + + /** 기존 팝업 실행 함수를 호출하고 차단·닫힘 상태를 버튼에 반영한다. */ + const handleOpenGisManagement = () => { + if (closeWatcherRef.current !== null) { + window.clearInterval(closeWatcherRef.current); + closeWatcherRef.current = null; + } + const popup = onOpenGisManagement(); + setIsGisManagementActive(Boolean(popup && !popup.closed)); + if (!popup || popup.closed) return; + + closeWatcherRef.current = window.setInterval(() => { + if (!popup.closed) return; + if (closeWatcherRef.current !== null) { + window.clearInterval(closeWatcherRef.current); + closeWatcherRef.current = null; + } + setIsGisManagementActive(false); + }, 300); + }; + + return ( +
+ + +
+ ); +} diff --git a/apps/sn/app/(map-layout)/map/lib/use-water-risk-layer.ts b/apps/sn/app/(map-layout)/map/lib/use-water-risk-layer.ts index 4af91499d..808f661dc 100644 --- a/apps/sn/app/(map-layout)/map/lib/use-water-risk-layer.ts +++ b/apps/sn/app/(map-layout)/map/lib/use-water-risk-layer.ts @@ -127,7 +127,7 @@ export function useWaterRiskLayer({ const [pixelX = 0, pixelY = 0] = map.getPixelFromCoordinate?.(waterRiskInfoAnchor) ?? []; const cardWidth = 244; - const cardHeight = 180; + const cardHeight = 190; const left = pixelX + 22; const top = pixelY - cardHeight / 2; @@ -148,6 +148,12 @@ export function useWaterRiskLayer({ setWaterRiskInfoCardStyle({ left, top }); }, [waterRiskInfoAnchor]); + /** 정보 카드만 닫고 지도 결과·범례는 유지하며, 다음 결과 선택 시 다시 표시한다. */ + const dismissWaterRiskInfoCard = useCallback(() => { + setWaterRiskInfoAnchor(null); + setWaterRiskInfoCardStyle(null); + }, []); + const clearWaterRiskPopupCloseWatcher = useCallback(() => { if (waterRiskPopupCloseWatcherRef.current !== null) { window.clearInterval(waterRiskPopupCloseWatcherRef.current); @@ -470,6 +476,7 @@ export function useWaterRiskLayer({ waterRiskInfoCardStyle, selectedWaterRiskMapView, selectedWaterRiskDisplayStatus, + dismissWaterRiskInfoCard, watchPopup, }; } diff --git a/apps/sn/app/(map-layout)/map/ui/map-control-styles.tsx b/apps/sn/app/(map-layout)/map/ui/map-control-styles.tsx index 37b05ee4b..52bab67d1 100644 --- a/apps/sn/app/(map-layout)/map/ui/map-control-styles.tsx +++ b/apps/sn/app/(map-layout)/map/ui/map-control-styles.tsx @@ -1,6 +1,6 @@ "use client"; -/** 지도 route에만 적용하는 기존 전역 컨트롤 CSS. 선택자와 스타일 값은 유지한다. */ +/** 지도 route의 기존 배치를 유지하고 SN 컨트롤의 쌓임 순서를 맞춘다. */ export default function MapControlStyles() { return (