|
|
|
@@ -168,14 +168,23 @@ const WATER_RISK_GRID_LAYER_ID = "SN_WATER_RISK_GRID";
|
|
|
|
|
const WATER_RISK_PIPE_LAYER_ID = "SN_WATER_RISK_PIPE";
|
|
|
|
|
const FLOOD_HISTORY_SELECTION_PADDING: [number, number, number, number] = [100, 380, 120, 120];
|
|
|
|
|
const RIGHT_PANEL_TAB_LIMIT = 5;
|
|
|
|
|
const RIGHT_PANEL_PIPE_TAB_ID = "feature:tb_ms_swage_ppl:singleton";
|
|
|
|
|
|
|
|
|
|
type RightPanelTabKind = "feature" | "sinkhole" | "gisEdit";
|
|
|
|
|
type RightPanelTabCategory =
|
|
|
|
|
| "sinkhole"
|
|
|
|
|
| "pipe"
|
|
|
|
|
| "treatmentFacility"
|
|
|
|
|
| "treatmentZone"
|
|
|
|
|
| "defaultFeature"
|
|
|
|
|
| "gisEdit";
|
|
|
|
|
|
|
|
|
|
type RightPanelTab = {
|
|
|
|
|
id: string;
|
|
|
|
|
kind: RightPanelTabKind;
|
|
|
|
|
title: string;
|
|
|
|
|
subTitle?: string;
|
|
|
|
|
category: RightPanelTabCategory;
|
|
|
|
|
displayLabel: string;
|
|
|
|
|
parentTabId?: string | null;
|
|
|
|
|
featureData: any;
|
|
|
|
|
sinkholeDetail?: SinkholeDetail | null;
|
|
|
|
|
nearbyPipes?: SinkholeNearbyPipe[];
|
|
|
|
@@ -183,10 +192,47 @@ type RightPanelTab = {
|
|
|
|
|
lastActiveAt: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type RightPanelTabStyle = {
|
|
|
|
|
active: string;
|
|
|
|
|
inactive: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type RightPanelBookmarkTabsProps = {
|
|
|
|
|
tabs: RightPanelTab[];
|
|
|
|
|
activeTabId: string | null;
|
|
|
|
|
onActivate: (tabId: string) => void;
|
|
|
|
|
onBack: (tabId: string) => void;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 유형별 의미가 색상과 탭 이름에 함께 드러나도록 정적 class를 사용한다.
|
|
|
|
|
const RIGHT_PANEL_TAB_STYLE_BY_CATEGORY: Record<
|
|
|
|
|
RightPanelTabCategory,
|
|
|
|
|
RightPanelTabStyle
|
|
|
|
|
> = {
|
|
|
|
|
sinkhole: {
|
|
|
|
|
active: "border-[#d97706] bg-[#d97706] text-white",
|
|
|
|
|
inactive: "border-[#fdba74] bg-[#fff7ed] text-[#9a3412] hover:bg-[#ffedd5]",
|
|
|
|
|
},
|
|
|
|
|
pipe: {
|
|
|
|
|
active: "border-[#2563eb] bg-[#2563eb] text-white",
|
|
|
|
|
inactive: "border-[#93c5fd] bg-[#eff6ff] text-[#1e40af] hover:bg-[#dbeafe]",
|
|
|
|
|
},
|
|
|
|
|
treatmentFacility: {
|
|
|
|
|
active: "border-[#15803d] bg-[#15803d] text-white",
|
|
|
|
|
inactive: "border-[#86efac] bg-[#f0fdf4] text-[#166534] hover:bg-[#dcfce7]",
|
|
|
|
|
},
|
|
|
|
|
treatmentZone: {
|
|
|
|
|
active: "border-[#7e22ce] bg-[#7e22ce] text-white",
|
|
|
|
|
inactive: "border-[#d8b4fe] bg-[#faf5ff] text-[#6b21a8] hover:bg-[#f3e8ff]",
|
|
|
|
|
},
|
|
|
|
|
gisEdit: {
|
|
|
|
|
active: "border-[#0f766e] bg-[#0f766e] text-white",
|
|
|
|
|
inactive: "border-[#5eead4] bg-[#f0fdfa] text-[#115e59] hover:bg-[#ccfbf1]",
|
|
|
|
|
},
|
|
|
|
|
defaultFeature: {
|
|
|
|
|
active: "border-[#475569] bg-[#475569] text-white",
|
|
|
|
|
inactive: "border-[#cbd5e1] bg-[#f8fafc] text-[#334155] hover:bg-[#f1f5f9]",
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 탭 제한을 넘으면 새 탭과 현재 탭을 보존하고 오래된 일반 상세부터 정리한다.
|
|
|
|
@@ -196,6 +242,14 @@ const findRightPanelTabOverflowCandidate = (
|
|
|
|
|
activeTabId: string | null,
|
|
|
|
|
) => {
|
|
|
|
|
const protectedTabIds = new Set([nextTabId, activeTabId].filter(Boolean) as string[]);
|
|
|
|
|
[nextTabId, activeTabId].forEach((tabId) => {
|
|
|
|
|
const pipeTab = tabs.find((tab) => tab.id === tabId && tab.category === "pipe");
|
|
|
|
|
const parentTabId = pipeTab?.parentTabId;
|
|
|
|
|
if (parentTabId && tabs.some((tab) => tab.id === parentTabId)) {
|
|
|
|
|
protectedTabIds.add(parentTabId);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const removableTabs = tabs
|
|
|
|
|
.filter((tab) => !protectedTabIds.has(tab.id))
|
|
|
|
|
.sort((a, b) => a.lastActiveAt - b.lastActiveAt);
|
|
|
|
@@ -210,34 +264,52 @@ const findRightPanelTabOverflowCandidate = (
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 오른쪽 상세 패널에 상세가 2개 이상 쌓였을 때만 책갈피형 전환 버튼을 보여준다.
|
|
|
|
|
const RightPanelBookmarkTabs = ({ tabs, activeTabId, onActivate }: RightPanelBookmarkTabsProps) => {
|
|
|
|
|
const RightPanelBookmarkTabs = ({
|
|
|
|
|
tabs,
|
|
|
|
|
activeTabId,
|
|
|
|
|
onActivate,
|
|
|
|
|
onBack,
|
|
|
|
|
}: RightPanelBookmarkTabsProps) => {
|
|
|
|
|
if (tabs.length < 2) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="absolute right-[350px] top-[92px] z-[70] flex w-[52px] flex-col items-end gap-2 pointer-events-auto">
|
|
|
|
|
<div className="pointer-events-auto absolute right-[350px] top-[92px] z-[70] flex w-[112px] flex-col items-end gap-2">
|
|
|
|
|
{tabs.map((tab) => {
|
|
|
|
|
const isActive = tab.id === activeTabId;
|
|
|
|
|
const style = RIGHT_PANEL_TAB_STYLE_BY_CATEGORY[tab.category];
|
|
|
|
|
const parentTab = tab.parentTabId
|
|
|
|
|
? tabs.find((candidate) => candidate.id === tab.parentTabId)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
<div
|
|
|
|
|
key={tab.id}
|
|
|
|
|
type="button"
|
|
|
|
|
className={[
|
|
|
|
|
"min-h-[38px] w-[46px] border border-r-0 px-1 py-1 text-center text-[0.95rem] font-semibold leading-tight shadow-sm transition-colors",
|
|
|
|
|
isActive
|
|
|
|
|
? "bg-primary text-white border-primary"
|
|
|
|
|
: "bg-white text-slate-900 border-[#9db8d8] hover:bg-[#e8f2ff]",
|
|
|
|
|
"flex min-h-[42px] w-[112px] items-center border border-r-0 px-1 py-1 shadow-sm transition-colors",
|
|
|
|
|
isActive ? style.active : style.inactive,
|
|
|
|
|
].join(" ")}
|
|
|
|
|
title={tab.subTitle ? `${tab.title} ${tab.subTitle}` : tab.title}
|
|
|
|
|
aria-pressed={isActive}
|
|
|
|
|
onClick={() => onActivate(tab.id)}
|
|
|
|
|
>
|
|
|
|
|
<span className="block break-keep">{tab.title}</span>
|
|
|
|
|
{tab.subTitle ? (
|
|
|
|
|
<span className="mt-[1px] block break-all text-[0.78rem] font-bold leading-none opacity-95">
|
|
|
|
|
{tab.subTitle}
|
|
|
|
|
</span>
|
|
|
|
|
{tab.category === "pipe" && isActive && parentTab ? (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="mr-1 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded text-sm font-bold transition-colors hover:bg-black/10 focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-1"
|
|
|
|
|
title={parentTab.displayLabel + "로 돌아가기"}
|
|
|
|
|
aria-label={parentTab.displayLabel + "로 돌아가기"}
|
|
|
|
|
onClick={() => onBack(parentTab.id)}
|
|
|
|
|
>
|
|
|
|
|
←
|
|
|
|
|
</button>
|
|
|
|
|
) : null}
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="inline-flex min-w-0 flex-1 self-stretch items-center justify-center px-1 text-center text-[0.82rem] font-semibold leading-tight"
|
|
|
|
|
title={tab.displayLabel}
|
|
|
|
|
aria-pressed={isActive}
|
|
|
|
|
onClick={() => onActivate(tab.id)}
|
|
|
|
|
>
|
|
|
|
|
<span className="block max-h-[2.5rem] overflow-hidden break-all">{tab.displayLabel}</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
@@ -860,23 +932,55 @@ const getRightPanelFeatureTitle = (data: any) => {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getRightPanelFeatureSubTitle = (data: any) => {
|
|
|
|
|
// OpenLayers Feature와 plain object 양쪽에서 탭 표시값 후보를 같은 순서로 읽는다.
|
|
|
|
|
const getRightPanelFeatureProperty = (data: any, keys: string[]) => {
|
|
|
|
|
for (const key of keys) {
|
|
|
|
|
const value = data?.get?.(key) ?? data?.values_?.[key] ?? data?.[key];
|
|
|
|
|
const normalizedValue = String(value ?? "").trim();
|
|
|
|
|
if (normalizedValue) return normalizedValue;
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getRightPanelTabCategory = (data: any): RightPanelTabCategory => {
|
|
|
|
|
const layerTypeName = getSelectedFeatureLayerTypeName(data);
|
|
|
|
|
if (layerTypeName !== "tb_ms_swage_ppl") return "";
|
|
|
|
|
if (layerTypeName === "tb_ms_swage_ppl") return "pipe";
|
|
|
|
|
if (layerTypeName === "tb_il_swage_prcs_fclt") return "treatmentFacility";
|
|
|
|
|
if (layerTypeName === "tb_ms_swage_prcs_zone") return "treatmentZone";
|
|
|
|
|
return "defaultFeature";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const pipeManagementNo =
|
|
|
|
|
data?.get?.("fclt_mng_no") ??
|
|
|
|
|
data?.get?.("fcltMngNo") ??
|
|
|
|
|
data?.get?.("pipeId") ??
|
|
|
|
|
data?.values_?.fclt_mng_no ??
|
|
|
|
|
data?.values_?.fcltMngNo ??
|
|
|
|
|
data?.values_?.pipeId ??
|
|
|
|
|
data?.fcltMngNo ??
|
|
|
|
|
data?.pipeId ??
|
|
|
|
|
data?.fid ??
|
|
|
|
|
"";
|
|
|
|
|
const getRightPanelFeatureDisplayLabel = (data: any, category: RightPanelTabCategory) => {
|
|
|
|
|
if (category === "pipe") {
|
|
|
|
|
const managementNo =
|
|
|
|
|
getRightPanelFeatureProperty(data, ["fclt_mng_no", "fcltMngNo", "pipeId", "fid"]) ||
|
|
|
|
|
getRightPanelFeatureIdentifier(data) ||
|
|
|
|
|
"-";
|
|
|
|
|
return `관로(${managementNo})`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return String(pipeManagementNo).trim();
|
|
|
|
|
if (category === "treatmentFacility") {
|
|
|
|
|
const facilityName =
|
|
|
|
|
getRightPanelFeatureProperty(data, ["swtrmt_fclt_nm", "swtrmtFcltNm"]) ||
|
|
|
|
|
getRightPanelFeatureProperty(data, ["swtrmt_fclt_cd", "swtrmtFcltCd"]) ||
|
|
|
|
|
"-";
|
|
|
|
|
return `처리시설(${facilityName})`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (category === "treatmentZone") {
|
|
|
|
|
const zoneName =
|
|
|
|
|
getRightPanelFeatureProperty(data, ["swtrmt_zone_nm", "swtrmtZoneNm"]) ||
|
|
|
|
|
getRightPanelFeatureProperty(data, [
|
|
|
|
|
"swtrmt_zone_sn",
|
|
|
|
|
"swtrmtZoneSn",
|
|
|
|
|
"swtrmt_fclt_cd",
|
|
|
|
|
"swtrmtFcltCd",
|
|
|
|
|
]) ||
|
|
|
|
|
"-";
|
|
|
|
|
return `처리구역(${zoneName})`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return getRightPanelFeatureTitle(data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const createFloodHistoryAreaStyle = (label: string) =>
|
|
|
|
@@ -2422,6 +2526,8 @@ const Map = () => {
|
|
|
|
|
// 오른쪽 상세 패널에 열린 상세 컴포넌트를 책갈피 탭 단위로 보관한다.
|
|
|
|
|
const [rightPanelTabs, setRightPanelTabs] = useState<RightPanelTab[]>([]);
|
|
|
|
|
const [activeRightPanelTabId, setActiveRightPanelTabId] = useState<string | null>(null);
|
|
|
|
|
// 단일 관로 탭이 어느 지반침하 상세에서 열렸는지 비동기 피처 조회 사이에 보관한다.
|
|
|
|
|
const pipeParentTabIdRef = useRef<string | null>(null);
|
|
|
|
|
const [isWaterDepthLayerActive, setIsWaterDepthLayerActive] = useState(false); // 침수심 레이어 활성 여부
|
|
|
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
|
|
|
const [mapReady, setMapReady] = useState(false);
|
|
|
|
@@ -2499,7 +2605,6 @@ const Map = () => {
|
|
|
|
|
const sinkholeSelectedSourceRef = useRef<VectorSource | null>(null);
|
|
|
|
|
const sinkholeFocusedPipeLayerRef = useRef<VectorLayer<any> | null>(null);
|
|
|
|
|
const sinkholeFocusedPipeSourceRef = useRef<VectorSource | null>(null);
|
|
|
|
|
const restoreSinkholeDetailOnCloseRef = useRef(false);
|
|
|
|
|
// 유하방향 레이어 체크 여부를 React 렌더링 흐름에 반영하기 위한 state이다.
|
|
|
|
|
const [isFlowDirectionChecked, setIsFlowDirectionChecked] = useState(false);
|
|
|
|
|
// 체크된 유하방향 트리 노드의 레이어 설정을 보관해 API 조회와 vector layer 스타일에 사용한다.
|
|
|
|
@@ -3596,6 +3701,10 @@ const Map = () => {
|
|
|
|
|
// SearchModal, GisContent 등에서 호출
|
|
|
|
|
const onFeatureSelect = (data: any) => {
|
|
|
|
|
const clickedLayerTypeName = getSelectedFeatureLayerTypeName(data);
|
|
|
|
|
if (clickedLayerTypeName === "tb_ms_swage_ppl") {
|
|
|
|
|
pipeParentTabIdRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 지형에 있는 피처를 직접 선택하면 지반침하 반경 내에서 만든 임시 관로 강조는 해제한다.
|
|
|
|
|
clearSinkholeFocusedPipeLayer();
|
|
|
|
|
if (rdiiResultMap.handleOverlayFeatureSelect(data)) {
|
|
|
|
@@ -3625,7 +3734,6 @@ const Map = () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (clickedLayerTypeName === SINKHOLE_LAYER_TYPE) {
|
|
|
|
|
restoreSinkholeDetailOnCloseRef.current = false;
|
|
|
|
|
const acdntNo = getSinkholeAccidentId(data);
|
|
|
|
|
if (acdntNo) {
|
|
|
|
|
void loadSinkholeDetail(acdntNo, false, getSinkholePointFromSelection(data), getSinkholeFallbackDetailFromSelection(data));
|
|
|
|
@@ -3836,8 +3944,6 @@ const Map = () => {
|
|
|
|
|
console.warn("OpenLayers Feature를 찾을 수 없습니다:", data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
restoreSinkholeDetailOnCloseRef.current =
|
|
|
|
|
clickedLayerTypeName === "tb_ms_swage_ppl" && Boolean(selectedSinkholeDetail);
|
|
|
|
|
|
|
|
|
|
setFeatureData(data);
|
|
|
|
|
setIsOpen(true);
|
|
|
|
@@ -4324,7 +4430,6 @@ const Map = () => {
|
|
|
|
|
clearSinkholeSelectionLayer();
|
|
|
|
|
clearSinkholeFocusedPipeLayer();
|
|
|
|
|
|
|
|
|
|
restoreSinkholeDetailOnCloseRef.current = false;
|
|
|
|
|
if (getSelectedFeatureLayerTypeName(featureData) === SINKHOLE_LAYER_TYPE) {
|
|
|
|
|
setFeatureData(null);
|
|
|
|
|
setIsOpen(false);
|
|
|
|
@@ -4530,6 +4635,11 @@ const Map = () => {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const activeSinkholeTab = rightPanelTabs.find(
|
|
|
|
|
(tab) => tab.id === activeRightPanelTabId && tab.category === "sinkhole",
|
|
|
|
|
);
|
|
|
|
|
pipeParentTabIdRef.current = activeSinkholeTab?.id ?? null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const layerTypeName = String(pipe?.lyrTypeNm ?? "tb_ms_swage_ppl");
|
|
|
|
|
const params = new URLSearchParams({
|
|
|
|
@@ -4551,7 +4661,6 @@ const Map = () => {
|
|
|
|
|
const firstFeature = features[0];
|
|
|
|
|
if (firstFeature) {
|
|
|
|
|
syncRightPanelFeatureSelectionOnMap(firstFeature);
|
|
|
|
|
restoreSinkholeDetailOnCloseRef.current = false;
|
|
|
|
|
setFeatureData(firstFeature);
|
|
|
|
|
setIsOpen(true);
|
|
|
|
|
setIsSearchModalOpen(false);
|
|
|
|
@@ -4560,7 +4669,12 @@ const Map = () => {
|
|
|
|
|
console.error("지반침하 반경 내 관로 지도 표시 실패:", error);
|
|
|
|
|
alert("선택한 관로를 지도에서 표시하지 못했습니다.");
|
|
|
|
|
}
|
|
|
|
|
}, [setIsSearchModalOpen, syncRightPanelFeatureSelectionOnMap]);
|
|
|
|
|
}, [
|
|
|
|
|
activeRightPanelTabId,
|
|
|
|
|
rightPanelTabs,
|
|
|
|
|
setIsSearchModalOpen,
|
|
|
|
|
syncRightPanelFeatureSelectionOnMap,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const buildRightPanelTab = useCallback(
|
|
|
|
|
(data: any): RightPanelTab | null => {
|
|
|
|
@@ -4577,8 +4691,9 @@ const Map = () => {
|
|
|
|
|
return {
|
|
|
|
|
id: `sinkhole:${acdntNo}`,
|
|
|
|
|
kind: "sinkhole",
|
|
|
|
|
title: "지반침하",
|
|
|
|
|
subTitle: acdntNo,
|
|
|
|
|
category: "sinkhole",
|
|
|
|
|
displayLabel: `지반침하(${acdntNo})`,
|
|
|
|
|
parentTabId: null,
|
|
|
|
|
featureData: {
|
|
|
|
|
lyrTypeNm: SINKHOLE_LAYER_TYPE,
|
|
|
|
|
acdntNo,
|
|
|
|
@@ -4593,19 +4708,26 @@ const Map = () => {
|
|
|
|
|
return {
|
|
|
|
|
id: `gisEdit:${gisEditDetailChangeId}`,
|
|
|
|
|
kind: "gisEdit",
|
|
|
|
|
title: "관로편집",
|
|
|
|
|
category: "gisEdit",
|
|
|
|
|
displayLabel: "관로편집",
|
|
|
|
|
parentTabId: null,
|
|
|
|
|
featureData: data,
|
|
|
|
|
gisEditChangeId: gisEditDetailChangeId,
|
|
|
|
|
lastActiveAt: now,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const category = getRightPanelTabCategory(data);
|
|
|
|
|
const featureId = getRightPanelFeatureIdentifier(data) || `${layerTypeName || "feature"}:${now}`;
|
|
|
|
|
return {
|
|
|
|
|
id: `feature:${layerTypeName || "unknown"}:${featureId}`,
|
|
|
|
|
id:
|
|
|
|
|
category === "pipe"
|
|
|
|
|
? RIGHT_PANEL_PIPE_TAB_ID
|
|
|
|
|
: `feature:${layerTypeName || "unknown"}:${featureId}`,
|
|
|
|
|
kind: "feature",
|
|
|
|
|
title: getRightPanelFeatureTitle(data),
|
|
|
|
|
subTitle: getRightPanelFeatureSubTitle(data),
|
|
|
|
|
category,
|
|
|
|
|
displayLabel: getRightPanelFeatureDisplayLabel(data, category),
|
|
|
|
|
parentTabId: category === "pipe" ? pipeParentTabIdRef.current : null,
|
|
|
|
|
featureData: data,
|
|
|
|
|
lastActiveAt: now,
|
|
|
|
|
};
|
|
|
|
@@ -4644,6 +4766,9 @@ const Map = () => {
|
|
|
|
|
currentTabs.map((tab) => (tab.id === tabId ? { ...tab, lastActiveAt: Date.now() } : tab)),
|
|
|
|
|
);
|
|
|
|
|
setActiveRightPanelTabId(tabId);
|
|
|
|
|
if (targetTab.category === "pipe") {
|
|
|
|
|
pipeParentTabIdRef.current = targetTab.parentTabId ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (targetTab.kind === "sinkhole") {
|
|
|
|
|
const detail = targetTab.sinkholeDetail ?? null;
|
|
|
|
@@ -4665,14 +4790,22 @@ const Map = () => {
|
|
|
|
|
setIsOpen(true);
|
|
|
|
|
setIsSearchModalOpen(false);
|
|
|
|
|
},
|
|
|
|
|
[renderSelectedSinkhole, rightPanelTabs, setIsSearchModalOpen, sinkholeRadiusMeters, syncRightPanelFeatureSelectionOnMap],
|
|
|
|
|
[
|
|
|
|
|
renderSelectedSinkhole,
|
|
|
|
|
rightPanelTabs,
|
|
|
|
|
setIsSearchModalOpen,
|
|
|
|
|
sinkholeRadiusMeters,
|
|
|
|
|
syncRightPanelFeatureSelectionOnMap,
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const closeRightPanelTab = useCallback(
|
|
|
|
|
(tabId: string | null) => {
|
|
|
|
|
if (!tabId) return;
|
|
|
|
|
const closingTab = rightPanelTabs.find((tab) => tab.id === tabId);
|
|
|
|
|
const remainingTabs = rightPanelTabs.filter((tab) => tab.id !== tabId);
|
|
|
|
|
const remainingTabs = rightPanelTabs
|
|
|
|
|
.filter((tab) => tab.id !== tabId)
|
|
|
|
|
.map((tab) => (tab.parentTabId === tabId ? { ...tab, parentTabId: null } : tab));
|
|
|
|
|
|
|
|
|
|
if (closingTab?.kind === "sinkhole") {
|
|
|
|
|
setSelectedSinkholeDetail(null);
|
|
|
|
@@ -4683,23 +4816,30 @@ const Map = () => {
|
|
|
|
|
const select = GisApp.Module.select as any;
|
|
|
|
|
select?.resetSelectedFeature?.();
|
|
|
|
|
clearSinkholeFocusedPipeLayer();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if (closingTab?.kind === "gisEdit") {
|
|
|
|
|
setGisEditDetailChangeId(null);
|
|
|
|
|
}
|
|
|
|
|
if (closingTab?.category === "pipe" || pipeParentTabIdRef.current === tabId) {
|
|
|
|
|
pipeParentTabIdRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setRightPanelTabs(remainingTabs);
|
|
|
|
|
|
|
|
|
|
if (remainingTabs.length === 0) {
|
|
|
|
|
setActiveRightPanelTabId(null);
|
|
|
|
|
restoreSinkholeDetailOnCloseRef.current = false;
|
|
|
|
|
pipeParentTabIdRef.current = null;
|
|
|
|
|
setFeatureData(null);
|
|
|
|
|
setIsOpen(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fallbackTab = [...remainingTabs].sort((a, b) => b.lastActiveAt - a.lastActiveAt)[0];
|
|
|
|
|
const parentFallbackTab =
|
|
|
|
|
closingTab?.category === "pipe" && closingTab.parentTabId
|
|
|
|
|
? remainingTabs.find((tab) => tab.id === closingTab.parentTabId)
|
|
|
|
|
: null;
|
|
|
|
|
const fallbackTab =
|
|
|
|
|
parentFallbackTab ?? [...remainingTabs].sort((a, b) => b.lastActiveAt - a.lastActiveAt)[0]!;
|
|
|
|
|
activateRightPanelTab(fallbackTab.id);
|
|
|
|
|
},
|
|
|
|
|
[activateRightPanelTab, clearSinkholeFocusedPipeLayer, clearSinkholeSelectionLayer, rightPanelTabs],
|
|
|
|
@@ -4713,6 +4853,7 @@ const Map = () => {
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (isOpen) return;
|
|
|
|
|
pipeParentTabIdRef.current = null;
|
|
|
|
|
setRightPanelTabs([]);
|
|
|
|
|
setActiveRightPanelTabId(null);
|
|
|
|
|
}, [isOpen]);
|
|
|
|
@@ -5817,6 +5958,7 @@ const Map = () => {
|
|
|
|
|
tabs={rightPanelTabs}
|
|
|
|
|
activeTabId={activeRightPanelTabId}
|
|
|
|
|
onActivate={activateRightPanelTab}
|
|
|
|
|
onBack={activateRightPanelTab}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
const closeActiveTab = () => {
|
|
|
|
@@ -5867,7 +6009,6 @@ const Map = () => {
|
|
|
|
|
featureData={panelFeatureData}
|
|
|
|
|
onClose={() => {
|
|
|
|
|
setGisEditDetailChangeId(null);
|
|
|
|
|
restoreSinkholeDetailOnCloseRef.current = false;
|
|
|
|
|
closeActiveTab();
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|