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 (