231 lines
7.0 KiB
TypeScript
231 lines
7.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState, type ComponentType, type ReactNode } from "react";
|
|
import {
|
|
ChartColumnBig,
|
|
ChevronDown,
|
|
ChevronUp,
|
|
PanelsTopLeft,
|
|
PencilRuler,
|
|
Search,
|
|
Settings2,
|
|
TriangleAlert,
|
|
Waves,
|
|
} from "lucide-react";
|
|
import {
|
|
windowPopupDefinitions,
|
|
type WindowPopupKey,
|
|
} from "@/features/windowPopup/model/popupDefinitions";
|
|
|
|
type Props = {
|
|
onOpenPopup: (popupKey: WindowPopupKey) => Window | null;
|
|
onOpenInfoSearch: () => void;
|
|
onToggleGisEdit?: () => void;
|
|
isGisEditActive?: boolean;
|
|
isInfoSearchActive?: boolean;
|
|
initialActiveKey?: WindowPopupKey | null;
|
|
};
|
|
|
|
type MenuIconProps = {
|
|
isActive: boolean;
|
|
};
|
|
|
|
const InftwtMenuIcon = ({ isActive }: MenuIconProps) => (
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
className={`h-[15px] w-[15px] shrink-0 ${isActive ? "text-primary" : "text-label"}`}
|
|
aria-hidden="true"
|
|
fill="none"
|
|
>
|
|
<path
|
|
d="M12 2.8C9.5 6.5 6.7 9.8 6.7 13.3A5.3 5.3 0 0 0 12 18.6a5.3 5.3 0 0 0 5.3-5.3c0-3.5-2.8-6.8-5.3-10.5Z"
|
|
fill="currentColor"
|
|
fillOpacity="0.14"
|
|
stroke="currentColor"
|
|
strokeWidth="1.8"
|
|
strokeLinejoin="round"
|
|
/>
|
|
<path
|
|
d="M9.5 13.4c0-1.2.8-2.6 1.9-4.2"
|
|
stroke="currentColor"
|
|
strokeWidth="1.6"
|
|
strokeLinecap="round"
|
|
opacity="0.9"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const ANALYSIS_POPUP_ITEMS: Array<{
|
|
key: WindowPopupKey | "infoSearch";
|
|
label: string;
|
|
icon?: ComponentType<{ className?: string }>;
|
|
renderIcon?: (props: MenuIconProps) => ReactNode;
|
|
}> = [
|
|
{
|
|
key: "infoSearch",
|
|
label: windowPopupDefinitions.mapSearch.title,
|
|
icon: Search,
|
|
},
|
|
{
|
|
key: "pipeAnalysisPopup",
|
|
label: windowPopupDefinitions.pipeAnalysisPopup.title,
|
|
icon: ChartColumnBig,
|
|
},
|
|
{
|
|
key: "floodPredictionMapPopup",
|
|
label: windowPopupDefinitions.floodPredictionMapPopup.title,
|
|
icon: Waves,
|
|
},
|
|
{
|
|
key: "agedPipeStatusByCtpvPopup",
|
|
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,
|
|
icon: TriangleAlert,
|
|
},
|
|
{
|
|
key: "inftwtResultPopup",
|
|
label: windowPopupDefinitions.inftwtResultPopup.title,
|
|
renderIcon: InftwtMenuIcon,
|
|
},
|
|
{
|
|
key: "rdiiResultPopup",
|
|
label: windowPopupDefinitions.rdiiResultPopup.title,
|
|
icon: ChartColumnBig,
|
|
},
|
|
];
|
|
|
|
export default function AnalysisPopupMenu({
|
|
onOpenPopup,
|
|
onOpenInfoSearch,
|
|
onToggleGisEdit,
|
|
isGisEditActive = false,
|
|
isInfoSearchActive = false,
|
|
initialActiveKey = null,
|
|
}: Props) {
|
|
const [isOpen, setIsOpen] = useState(true);
|
|
const [activeKey, setActiveKey] = useState<
|
|
WindowPopupKey | "infoSearch" | null
|
|
>(initialActiveKey);
|
|
|
|
useEffect(() => {
|
|
if (!isGisEditActive && activeKey === "gisEditPopup") {
|
|
setActiveKey(null);
|
|
}
|
|
}, [activeKey, isGisEditActive]);
|
|
|
|
useEffect(() => {
|
|
if (!isInfoSearchActive && activeKey === "infoSearch") {
|
|
setActiveKey(null);
|
|
}
|
|
}, [activeKey, isInfoSearchActive]);
|
|
|
|
useEffect(() => {
|
|
if (!initialActiveKey) return;
|
|
setActiveKey((currentKey) => currentKey ?? initialActiveKey);
|
|
}, [initialActiveKey]);
|
|
|
|
return (
|
|
<div className="absolute right-[15px] top-[8px] z-[45]">
|
|
<div className="flex w-[154px] flex-col overflow-hidden rounded-[6px] border border-border bg-background shadow-[0_8px_18px_rgba(15,23,42,0.14)]">
|
|
<div className="flex h-[36px] shrink-0 items-center justify-between gap-2 border-b border-border bg-secondary pl-[10px] pr-[4px] text-[13px] font-bold text-foreground">
|
|
<div className="flex items-center gap-2">
|
|
<PanelsTopLeft className="h-4 w-4 text-primary" />
|
|
<span>분석도구</span>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
className="flex h-[28px] w-[28px] shrink-0 items-center justify-center rounded-[4px] text-label transition hover:bg-accent hover:text-primary"
|
|
onClick={() => setIsOpen((value) => !value)}
|
|
title={isOpen ? "분석 메뉴 숨기기" : "분석 메뉴 보이기"}
|
|
aria-label={isOpen ? "분석 메뉴 숨기기" : "분석 메뉴 보이기"}
|
|
aria-expanded={isOpen}
|
|
>
|
|
{isOpen ? (
|
|
<ChevronUp className="h-4 w-4" />
|
|
) : (
|
|
<ChevronDown className="h-4 w-4" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{isOpen ? (
|
|
<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;
|
|
|
|
return (
|
|
<button
|
|
key={item.key}
|
|
type="button"
|
|
className={`relative flex h-[33px] items-center gap-[7px] rounded-[5px] border px-[8px] pl-[10px] text-left text-[12px] font-semibold transition ${
|
|
isActive
|
|
? "border-primary bg-secondary font-bold text-primary before:absolute before:bottom-[7px] before:left-0 before:top-[7px] before:w-[3px] before:rounded-r-[2px] before:bg-primary"
|
|
: "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") {
|
|
onOpenInfoSearch();
|
|
return;
|
|
}
|
|
|
|
const popupWindow = onOpenPopup(item.key);
|
|
if (!popupWindow) {
|
|
setActiveKey(null);
|
|
return;
|
|
}
|
|
|
|
const closeWatcher = window.setInterval(() => {
|
|
if (!popupWindow.closed) return;
|
|
window.clearInterval(closeWatcher);
|
|
setActiveKey((currentKey) =>
|
|
currentKey === item.key ? null : currentKey,
|
|
);
|
|
}, 300);
|
|
}}
|
|
>
|
|
{item.renderIcon ? item.renderIcon({ isActive }) : null}
|
|
{Icon ? (
|
|
<Icon
|
|
className={`h-[15px] w-[15px] shrink-0 ${
|
|
isActive ? "text-primary" : "text-label"
|
|
}`}
|
|
/>
|
|
) : null}
|
|
<span className="whitespace-nowrap">{item.label}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|