관로 분석 - 분석 도구 메뉴 및 지도 컨트롤 UI 정리

This commit is contained in:
kiminchan
2026-05-13 15:49:03 +09:00
parent c121698713
commit cec0701087
3 changed files with 88 additions and 26 deletions
@@ -1,50 +1,101 @@
"use client";
import { useState } from "react";
import {
ChartColumnBig,
ChevronLeft,
ChevronRight,
History,
PanelsTopLeft,
PencilRuler,
Search,
Settings2,
TriangleAlert,
Waves,
} from "lucide-react";
import type { WindowPopupKey } from "@/features/windowPopup/model/popupDefinitions";
type Props = {
onOpenPopup: (popupKey: WindowPopupKey) => void;
onOpenInfoSearch: () => void;
};
const ANALYSIS_POPUP_ITEMS: Array<{ key: WindowPopupKey; label: string }> = [
{ key: "pipeAnalysisPopup", label: "관로분석" },
{ key: "floodPredictionMapPopup", label: "침수예측지도" },
{ key: "floodHistoryPopup", label: "침수발생이력" },
{ key: "gisEditPopup", label: "GIS 편집" },
{ key: "gisManagementPopup", label: "GIS 관리" },
{ key: "flowRiskPopup", label: "통수 위험" },
const ANALYSIS_POPUP_ITEMS: Array<{
key: WindowPopupKey | "infoSearch";
label: string;
icon: typeof ChartColumnBig;
}> = [
{ key: "infoSearch", label: "정보검색", icon: Search },
{ key: "pipeAnalysisPopup", label: "관로분석", icon: ChartColumnBig },
{ key: "floodPredictionMapPopup", label: "침수예측지도", icon: Waves },
{ key: "floodHistoryPopup", label: "침수발생이력", icon: History },
{ key: "gisEditPopup", label: "GIS 편집", icon: PencilRuler },
{ key: "gisManagementPopup", label: "GIS 관리", icon: Settings2 },
{ key: "flowRiskPopup", label: "통수 위험", icon: TriangleAlert },
];
export default function AnalysisPopupMenu({ onOpenPopup }: Props) {
export default function AnalysisPopupMenu({ onOpenPopup, onOpenInfoSearch }: Props) {
const [isOpen, setIsOpen] = useState(true);
const [activeKey, setActiveKey] = useState<WindowPopupKey | "infoSearch" | null>(null);
return (
<div className="absolute right-[0.35rem] top-[53%] z-[70]">
<div className="absolute right-[0.55rem] top-[20px] z-[45]">
<div className="relative">
{isOpen ? (
<div className="absolute right-[21px] top-1/2 flex w-[86px] -translate-y-1/2 flex-col gap-[3px]">
{ANALYSIS_POPUP_ITEMS.map((item) => (
<button
key={item.key}
type="button"
className="min-h-[40px] rounded-md border border-primary bg-primary px-2 py-1 text-center text-[1rem] font-semibold leading-[1.2] text-white shadow transition hover:brightness-95"
onClick={() => onOpenPopup(item.key)}
>
<span className="break-keep whitespace-normal">{item.label}</span>
</button>
))}
<div className="absolute right-[28px] w-[132px] overflow-hidden rounded-[6px] border border-[#d8dee8] bg-[rgba(255,255,255,0.96)] shadow-[0_8px_18px_rgba(15,23,42,0.14)]">
<div className="flex h-[36px] items-center gap-2 border-b border-[#e5eaf1] bg-[#f8fafc] px-[10px] text-[13px] font-bold text-[#1f2937]">
<div className="flex items-center gap-2">
<PanelsTopLeft className="h-4 w-4 text-[#1e88e5]" />
<span>분석도구</span>
</div>
</div>
<div className="flex flex-col gap-1 p-[6px]">
{ANALYSIS_POPUP_ITEMS.map((item) => {
const Icon = item.icon;
const isActive = activeKey === item.key;
return (
<button
key={item.key}
type="button"
className={`relative flex h-[34px] items-center gap-[7px] rounded-[5px] border px-[8px] pl-[10px] text-left text-[12px] font-semibold transition ${
isActive
? "border-[#b9dcff] bg-[#eaf4ff] font-bold text-[#0f5fa8] before:absolute before:left-0 before:top-[7px] before:bottom-[7px] before:w-[3px] before:rounded-r-[2px] before:bg-[#1e88e5]"
: "border-transparent bg-white text-[#374151] hover:border-[#cfe5ff] hover:bg-[#eef6ff] hover:text-[#1e3a5f]"
}`}
onClick={() => {
setActiveKey(item.key);
if (item.key === "infoSearch") {
onOpenInfoSearch();
return;
}
onOpenPopup(item.key);
}}
>
<Icon
className={`h-[15px] w-[15px] shrink-0 ${
isActive ? "text-[#1e88e5]" : "text-[#64748b]"
}`}
/>
<span className="break-keep whitespace-normal">{item.label}</span>
</button>
);
})}
</div>
</div>
) : null}
<button
type="button"
className="flex h-[36px] w-[18px] items-center justify-center rounded-sm border border-[#2f5c9b] bg-white text-[0.9rem] font-bold text-[#2f5c9b] shadow transition hover:bg-slate-50"
className="absolute right-0 top-0 flex h-[36px] w-[20px] items-center justify-center rounded-[4px] border border-[#d8dee8] bg-[rgba(255,255,255,0.96)] text-[#64748b] shadow-[0_8px_18px_rgba(15,23,42,0.14)] transition hover:bg-[#eaf4ff] hover:text-[#1e88e5]"
onClick={() => setIsOpen((value) => !value)}
title={isOpen ? "분석 메뉴 접기" : "분석 메뉴 펼치기"}
aria-label={isOpen ? "분석 메뉴 접기" : "분석 메뉴 펼치기"}
>
{isOpen ? ">" : "<"}
{isOpen ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
</button>
</div>
</div>
@@ -70,6 +70,10 @@ function Gis({
viewExtent,
viewMinZoom,
showZoomLevel = false,
zoomGuide,
showFloatingSearchButton = true,
treeTogglePosition = "center",
showZoomScaleInfo = false,
}) {
const mapRef = useRef();
const [mapObject, setMapObject] = useState({});
@@ -311,6 +315,7 @@ function Gis({
onProgressChange={handleProgressChange}
layerSettingView={LayerSettingView}
inlineLayerSettingView={inlineLayerSettingView}
toggleButtonPosition={treeTogglePosition}
/>
{typeof WaterDepth === "function" && (
@@ -345,13 +350,15 @@ function Gis({
</div>
)}
<GisToolBar />
{showZoomLevel && <GisZoomLevel />}
{showZoomLevel && (
<GisZoomLevel zoomGuide={zoomGuide} showZoomScaleInfo={showZoomScaleInfo} />
)}
<GisMapToggle />
<GisSelect onFeatureSelect={handleFeatureSelect} />
</div>
</div>
{isSearch && (
{isSearch && showFloatingSearchButton && (
<div className="absolute right-8 top-[248px] z-20">
<button
onClick={() => setIsSearchModalOpen(true)}
@@ -46,6 +46,7 @@ export default React.memo(function GisTree({
layerSettingView: LayerSettingView = null,
layerIconVisible,
inlineLayerSettingView,
toggleButtonPosition = "center",
}) {
const { gisCore, treeData } = useGisContext();
@@ -510,6 +511,9 @@ export default React.memo(function GisTree({
if (!treeData || treeData.length === 0) return null;
const toggleButtonPositionClass =
toggleButtonPosition === "top" ? "top-[5.5rem]" : "top-1/2 -translate-y-1/2";
return (
<>
{/* 사이드 패널 */}
@@ -629,7 +633,7 @@ export default React.memo(function GisTree({
)}
<button
onClick={() => setVisible(false)}
className="absolute right-[-3px] top-1/2 z-50 -translate-y-1/2 transform rounded-l-none rounded-r border border-l border-gray-300 bg-white px-1 py-2 shadow-sm"
className={`absolute right-[-3px] z-50 transform rounded-l-none rounded-r border border-l border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
>
<ChevronLeft size={16} />
</button>
@@ -637,7 +641,7 @@ export default React.memo(function GisTree({
{!visible && (
<button
onClick={() => setVisible(true)}
className="absolute left-[-3px] top-1/2 z-50 -translate-y-1/2 transform rounded-r border border-r border-gray-300 bg-white px-1 py-2 shadow-sm"
className={`absolute left-[-3px] z-50 transform rounded-r border border-r border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
>
<ChevronRight size={16} />
</button>