sn: GIS편집,GIS관리, 통수위험 정보카드 X버튼
This commit is contained in:
@@ -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({
|
||||
<div className="flex flex-col gap-[3px] p-[6px]">
|
||||
{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") {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<number | null>(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 (
|
||||
<div
|
||||
role="group"
|
||||
aria-label="GIS 도구"
|
||||
className="absolute right-[16px] top-[222.67px] z-[45] flex w-[40px] flex-col overflow-hidden rounded-[3px] bg-background shadow-[0_1px_2px_rgba(60,64,67,0.3),0_1px_3px_1px_rgba(60,64,67,0.15)]"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
title="GIS 편집"
|
||||
aria-label="GIS 편집"
|
||||
aria-pressed={isGisEditActive}
|
||||
onClick={onToggleGisEdit}
|
||||
className={`${getButtonClass(isGisEditActive)} border-b border-[#eeeeee]`}
|
||||
>
|
||||
<PencilRuler className="h-[20px] w-[20px]" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
title="GIS 관리"
|
||||
aria-label="GIS 관리"
|
||||
aria-haspopup="dialog"
|
||||
className={getButtonClass(isGisManagementActive)}
|
||||
onClick={handleOpenGisManagement}
|
||||
>
|
||||
<Settings2 className="h-[20px] w-[20px]" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
/** 지도 route에만 적용하는 기존 전역 컨트롤 CSS. 선택자와 스타일 값은 유지한다. */
|
||||
/** 지도 route의 기존 배치를 유지하고 SN 컨트롤의 쌓임 순서를 맞춘다. */
|
||||
export default function MapControlStyles() {
|
||||
return (
|
||||
<style jsx global>{`
|
||||
@@ -8,6 +8,7 @@ export default function MapControlStyles() {
|
||||
top: 16px;
|
||||
right: 120px;
|
||||
bottom: auto;
|
||||
z-index: 45;
|
||||
}
|
||||
|
||||
.sn-map-control-layout .map-type-tabs,
|
||||
@@ -28,6 +29,7 @@ export default function MapControlStyles() {
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
bottom: auto;
|
||||
z-index: 45;
|
||||
}
|
||||
|
||||
.sn-map-control-layout .tool-bar-vector-icons ul li div {
|
||||
|
||||
@@ -63,7 +63,7 @@ import {
|
||||
normalizeRainObservationLayerInteraction,
|
||||
getFlowDirectionMinZoom,
|
||||
} from "@/features/map-layers";
|
||||
import { ListTree } from "lucide-react";
|
||||
import { ListTree, X } from "lucide-react";
|
||||
import {
|
||||
Fragment,
|
||||
useCallback,
|
||||
@@ -184,6 +184,8 @@ import {
|
||||
} from "../water-depth/water-depth";
|
||||
import { buildWaterRiskStatusLabel } from "../components/water-risk/water-risk-types";
|
||||
|
||||
import GisToolButtons from "../components/gis-tools/gis-tool-buttons";
|
||||
|
||||
const AnalysisPopupMenu = dynamic(
|
||||
() => import("../components/analysis-menu/analysis-popup-menu"),
|
||||
{ ssr: false },
|
||||
@@ -296,6 +298,7 @@ const MapPageClient = () => {
|
||||
waterRiskInfoCardStyle,
|
||||
selectedWaterRiskMapView,
|
||||
selectedWaterRiskDisplayStatus,
|
||||
dismissWaterRiskInfoCard,
|
||||
watchPopup: watchWaterRiskPopup,
|
||||
} = useWaterRiskLayer({
|
||||
gisMaps,
|
||||
@@ -3128,17 +3131,27 @@ const MapPageClient = () => {
|
||||
style={waterRiskInfoCardStyle}
|
||||
className="absolute z-[44] w-[244px] overflow-hidden rounded-[12px] border border-border bg-background shadow-blur"
|
||||
>
|
||||
<div className="flex items-center justify-between bg-secondary px-[9px] py-[7px]">
|
||||
<div>
|
||||
<div className="bg-secondary px-[9px] py-[7px]">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-[10px] font-semibold text-muted-foreground">
|
||||
중점관리지역
|
||||
</div>
|
||||
<div className="mt-[2px] text-[13px] font-bold text-foreground">
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-[20px] w-[20px] items-center justify-center rounded-[3px] text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
onClick={dismissWaterRiskInfoCard}
|
||||
aria-label="통수위험 정보 카드 닫기"
|
||||
title="닫기"
|
||||
>
|
||||
<X className="h-[14px] w-[14px]" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-[2px] flex items-center justify-between gap-2">
|
||||
<div className="min-w-0 text-[13px] font-bold text-foreground">
|
||||
{selectedWaterRiskResultRow.areaName}
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`inline-flex min-w-[48px] items-center justify-center rounded-full px-[7px] py-[5px] text-[10px] font-bold ${
|
||||
className={`inline-flex min-w-[48px] shrink-0 items-center justify-center rounded-full px-[7px] py-[5px] text-[10px] font-bold ${
|
||||
selectedWaterRiskDisplayStatus === "DANGER"
|
||||
? "bg-[#ff1c15] text-white"
|
||||
: "bg-[#e8f6f4] text-[#0f766e]"
|
||||
@@ -3147,6 +3160,7 @@ const MapPageClient = () => {
|
||||
{buildWaterRiskStatusLabel(selectedWaterRiskDisplayStatus)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-background px-[9px] py-[6px] text-[9px] text-muted-foreground">
|
||||
<div>GIS 매핑 관로 중 Capacity 0.8 이상이 1개라도 있으면</div>
|
||||
<div>
|
||||
@@ -3236,10 +3250,13 @@ const MapPageClient = () => {
|
||||
initialActiveKey={initialAnalysisPopupKey}
|
||||
onOpenPopup={openAnalysisPopup}
|
||||
onOpenInfoSearch={() => setIsSearchModalOpen(true)}
|
||||
onToggleGisEdit={handleToggleGisEdit}
|
||||
isGisEditActive={gisEdit.isEditMode}
|
||||
isInfoSearchActive={isSearchModalOpen}
|
||||
/>
|
||||
<GisToolButtons
|
||||
isGisEditActive={gisEdit.isEditMode}
|
||||
onToggleGisEdit={handleToggleGisEdit}
|
||||
onOpenGisManagement={() => openAnalysisPopup("gisManagementPopup")}
|
||||
/>
|
||||
{isGisEditSessionInlineVisible ? (
|
||||
<div
|
||||
data-testid="gis-edit-session-inline"
|
||||
|
||||
Reference in New Issue
Block a user