지반침하 연계 관로 탭의 부모 종속 제거 처리
This commit is contained in:
@@ -170,7 +170,8 @@ 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";
|
||||
// Only pipes opened from sinkhole details share one slot within the parent-return flow.
|
||||
const RIGHT_PANEL_SINKHOLE_PIPE_TAB_ID = "feature:tb_ms_swage_ppl:sinkhole-singleton";
|
||||
|
||||
type RightPanelTabKind = "feature" | "sinkhole" | "gisEdit";
|
||||
type RightPanelTabCategory =
|
||||
@@ -237,34 +238,20 @@ const RIGHT_PANEL_TAB_STYLE_BY_CATEGORY: Record<
|
||||
},
|
||||
};
|
||||
|
||||
// 탭 제한을 넘으면 새 탭과 현재 탭을 보존하고 오래된 일반 상세부터 정리한다.
|
||||
// 공용 우측 탭은 유형과 활성 상태에 관계없이 먼저 열린 탭부터 제거한다.
|
||||
const findRightPanelTabOverflowCandidate = (
|
||||
tabs: RightPanelTab[],
|
||||
nextTabId: string,
|
||||
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);
|
||||
|
||||
return (
|
||||
removableTabs.find((tab) => tab.kind === "feature") ??
|
||||
removableTabs.find((tab) => tab.kind !== "gisEdit") ??
|
||||
removableTabs[0] ??
|
||||
tabs.filter((tab) => tab.id !== nextTabId).sort((a, b) => a.lastActiveAt - b.lastActiveAt)[0] ??
|
||||
null
|
||||
);
|
||||
return tabs.find((tab) => tab.id !== nextTabId) ?? null;
|
||||
};
|
||||
|
||||
// 부모 지반침하가 제거되면 그 부모에서 열린 연계 관로도 같은 탭 그룹으로 제거한다.
|
||||
const removeRightPanelTabGroup = (tabs: RightPanelTab[], removedTabId: string) =>
|
||||
tabs.filter(
|
||||
(tab) => tab.id !== removedTabId && tab.parentTabId !== removedTabId,
|
||||
);
|
||||
|
||||
// 오른쪽 상세 패널에 상세가 2개 이상 쌓였을 때만 책갈피형 전환 버튼을 보여준다.
|
||||
const RightPanelBookmarkTabs = ({
|
||||
tabs,
|
||||
@@ -291,7 +278,7 @@ const RightPanelBookmarkTabs = ({
|
||||
isActive ? style.active : style.inactive,
|
||||
].join(" ")}
|
||||
>
|
||||
{tab.category === "pipe" && isActive && parentTab ? (
|
||||
{tab.category === "pipe" && 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"
|
||||
@@ -4750,15 +4737,17 @@ const Map = () => {
|
||||
|
||||
const category = getRightPanelTabCategory(data);
|
||||
const featureId = getRightPanelFeatureIdentifier(data) || `${layerTypeName || "feature"}:${now}`;
|
||||
// Direct map selections accumulate per pipe; only a pipe with a sinkhole parent replaces the linked slot.
|
||||
const pipeParentTabId = category === "pipe" ? pipeParentTabIdRef.current : null;
|
||||
return {
|
||||
id:
|
||||
category === "pipe"
|
||||
? RIGHT_PANEL_PIPE_TAB_ID
|
||||
category === "pipe" && pipeParentTabId
|
||||
? RIGHT_PANEL_SINKHOLE_PIPE_TAB_ID
|
||||
: `feature:${layerTypeName || "unknown"}:${featureId}`,
|
||||
kind: "feature",
|
||||
category,
|
||||
displayLabel: getRightPanelFeatureDisplayLabel(data, category),
|
||||
parentTabId: category === "pipe" ? pipeParentTabIdRef.current : null,
|
||||
parentTabId: pipeParentTabId,
|
||||
featureData: data,
|
||||
lastActiveAt: now,
|
||||
};
|
||||
@@ -4779,14 +4768,13 @@ const Map = () => {
|
||||
const overflowCandidate = findRightPanelTabOverflowCandidate(
|
||||
mergedTabs,
|
||||
nextTab.id,
|
||||
activeRightPanelTabId,
|
||||
);
|
||||
if (!overflowCandidate) return mergedTabs.slice(-RIGHT_PANEL_TAB_LIMIT);
|
||||
|
||||
return mergedTabs.filter((item) => item.id !== overflowCandidate.id);
|
||||
return removeRightPanelTabGroup(mergedTabs, overflowCandidate.id);
|
||||
});
|
||||
setActiveRightPanelTabId(tab.id);
|
||||
}, [activeRightPanelTabId]);
|
||||
}, []);
|
||||
|
||||
const activateRightPanelTab = useCallback(
|
||||
(tabId: string) => {
|
||||
@@ -4834,9 +4822,7 @@ const Map = () => {
|
||||
(tabId: string | null) => {
|
||||
if (!tabId) return;
|
||||
const closingTab = rightPanelTabs.find((tab) => tab.id === tabId);
|
||||
const remainingTabs = rightPanelTabs
|
||||
.filter((tab) => tab.id !== tabId)
|
||||
.map((tab) => (tab.parentTabId === tabId ? { ...tab, parentTabId: null } : tab));
|
||||
const remainingTabs = removeRightPanelTabGroup(rightPanelTabs, tabId);
|
||||
|
||||
if (closingTab?.kind === "sinkhole") {
|
||||
setSelectedSinkholeDetail(null);
|
||||
@@ -4886,11 +4872,9 @@ const Map = () => {
|
||||
if (hasSinkholeTab && activeRightPanelTabId === sinkholeTabId) {
|
||||
closeRightPanelTab(sinkholeTabId);
|
||||
} else {
|
||||
// 비활성 지반침하 탭은 현재 활성 탭을 바꾸지 않고 제거하며 종속 관로의 부모 연결만 해제한다.
|
||||
// 비활성 지반침하 탭은 현재 활성 탭을 바꾸지 않고 종속 관로와 함께 제거한다.
|
||||
setRightPanelTabs((currentTabs) =>
|
||||
currentTabs
|
||||
.filter((tab) => tab.id !== sinkholeTabId)
|
||||
.map((tab) => (tab.parentTabId === sinkholeTabId ? { ...tab, parentTabId: null } : tab)),
|
||||
removeRightPanelTabGroup(currentTabs, sinkholeTabId),
|
||||
);
|
||||
setSelectedSinkholeDetail(null);
|
||||
setSelectedSinkholeNearbyPipes([]);
|
||||
|
||||
@@ -128,3 +128,15 @@
|
||||
7. 해제 후 다른 지반침하 포인트를 다시 선택해 상세와 반경 표시가 정상적으로 열리는지 확인한다.
|
||||
|
||||
- 이 절차는 지도 포인트 선택 후 해제를 검증한다. 목록 행 선택만 한 상태는 지도 선택 모듈의 빈 선택 이벤트 대상이 아니므로 별도 시나리오로 구분한다.
|
||||
|
||||
### 3.8 2026-07-15 관로 탭 진입 경로 분리 검증 계획
|
||||
|
||||
1. 지반침하와 무관한 지도 관로 A, B를 차례로 직접 선택해 관로 탭 2개가 각각 유지되는지 확인한다.
|
||||
2. 같은 일반 관로를 다시 선택하면 중복 탭을 만들지 않고 기존 탭이 활성화되는지 확인한다.
|
||||
3. 지반침하 상세에서 반경 내 관로 A, B를 차례로 선택해 부모 화살표가 있는 관로 탭 1개의 표시명과 상세만 B로 교체되는지 확인한다.
|
||||
4. 화살표를 클릭하면 직전 부모 지반침하 탭으로 복귀하는지 확인한다.
|
||||
5. 일반 관로 탭을 활성화해도 비활성 지반침하 연계 관로 탭의 화살표가 계속 표시되는지 확인한다.
|
||||
6. 지도에서 직접 선택한 일반 관로 탭에는 화살표가 표시되지 않는지 확인한다.
|
||||
7. `지반침하 + 연계 관로 + 일반 관로 3개` 상태에서 새 일반 관로를 열면 가장 오래된 부모 지반침하와 연계 관로가 함께 제거되고 일반 관로 4개가 남는지 확인한다.
|
||||
8. 연계 관로 탭만 먼저 FIFO 또는 수동으로 제거할 때 부모 지반침하 탭은 유지되는지 확인한다.
|
||||
9. 정적 검사는 TSX 구문 진단과 `git diff --check`로 수행하고, 상호작용은 로그인 가능한 `/map`에서 수동 확인한다.
|
||||
|
||||
@@ -321,3 +321,15 @@
|
||||
- 지도에서 선택한 지반침하가 빈 지도 영역 클릭으로 정상 해제되고 관련 반경·강조 표시도 함께 정리됨을 확인했다.
|
||||
- 지반침하발생현황 목록 첫 열에 사고번호가 표시되고 화면 `NO` 열이 제거된 것을 확인했다.
|
||||
- 이번 유지보수 요구사항 전체가 성공했으므로 최종 판정은 `완료`다.
|
||||
|
||||
### 2.30 2026-07-15 관로 탭 진입 경로 분리 정적 검증 결과
|
||||
|
||||
- `buildRightPanelTab`에서 부모가 있는 관로만 `RIGHT_PANEL_SINKHOLE_PIPE_TAB_ID`를 사용하고 일반 관로는 피처 식별값 기반 ID를 사용하는 것을 확인했다.
|
||||
- 지도 직접 선택 시 `pipeParentTabIdRef.current = null`로 부모 연결을 해제하는 기존 경로를 확인했다.
|
||||
- 부모 탭이 있는 관로는 활성 여부와 무관하게 `←` 버튼을 렌더링하도록 변경된 것을 확인했다.
|
||||
- 자동 정리 후보가 탭 배열의 첫 항목을 선택해 유형·활성 상태와 관계없이 FIFO로 동작하는 것을 확인했다.
|
||||
- `removeRightPanelTabGroup`이 제거 대상 ID와 그 ID를 `parentTabId`로 가진 탭을 함께 제외해 부모 지반침하 제거 시 연계 관로도 사라지는 것을 확인했다.
|
||||
- 연계 관로 자체를 제거 대상으로 전달하면 부모 탭은 조건에 걸리지 않아 유지되는 단방향 종속 구조를 확인했다.
|
||||
- TypeScript compiler API의 `transpileModule`로 `page.tsx`를 TSX 파싱한 결과 error diagnostics 0건이었다.
|
||||
- 프론트 변경 파일 대상 `git diff --check`는 통과했다.
|
||||
- 로그인 세션이 필요한 실제 탭 누적·교체·복귀 상호작용은 수동 검증이 남아 있다.
|
||||
|
||||
@@ -264,3 +264,12 @@
|
||||
- `packages/common/business_components/src/ui/map/lib/gis.select.js`
|
||||
|
||||
- BE API·DTO·Repository·DB는 변경하지 않았다.
|
||||
|
||||
## 15. 2026-07-15 관로 탭 누적 정책 후속 변경
|
||||
|
||||
- 기존의 관로 전역 단일 탭 정책을 진입 경로별 정책으로 변경한다.
|
||||
- 지도에서 직접 선택한 일반 관로는 관로 식별값별 탭으로 누적한다.
|
||||
- 지반침하 상세의 반경 내 관로에서 선택한 관로는 부모 지반침하가 있는 단일 슬롯을 공유하며, 다음 관로 선택 시 같은 탭의 상세를 교체한다.
|
||||
- 부모가 있는 관로 탭은 활성 여부와 무관하게 `←` 버튼을 항상 표시해 일반 관로와 구분하고 지반침하 상세로 복귀한다.
|
||||
- 공용 우측 탭은 최대 5개 FIFO를 적용하되, 제거 대상이 부모 지반침하이면 그 부모에서 열린 연계 관로도 함께 제거한다. 연계 관로만 제거될 때는 부모 지반침하를 유지한다.
|
||||
- 변경 범위는 `app/(map-layout)/map/page.tsx`이며 BE API·DTO·DB 계약은 변경하지 않는다.
|
||||
|
||||
@@ -490,3 +490,12 @@
|
||||
- 지도 지반침하 선택 후 빈 지도 영역 클릭 시 선택 상세, 반경 원·라벨, 종속 강조가 정상 해제되고 다른 탭과 기본 레이어는 유지됐다.
|
||||
- 지반침하발생현황 목록은 사고번호가 첫 열로 표시되고 화면 `NO` 열은 제거된 상태로 정상 동작했다.
|
||||
- BE API·DTO·Repository·DB 계약 변경 없이 최종 상태를 `완료`로 전환했다.
|
||||
|
||||
### 4.57 2026-07-15 일반 관로 다중·지반침하 연계 관로 단일 탭 분리
|
||||
|
||||
- 원인은 `buildRightPanelTab`이 모든 하수관로에 전역 singleton ID를 부여해 일반 지도 클릭도 기존 관로 탭을 덮어쓰던 것이었다.
|
||||
- 일반 지도 선택은 `onFeatureSelect`에서 부모 참조를 비우는 기존 흐름을 유지하고, 관로 식별값 기반 탭 ID를 사용하도록 변경했다.
|
||||
- 지반침하 상세의 반경 내 관로 선택은 `parentTabId`가 있을 때만 `sinkhole-singleton` ID를 사용해 관련 관로 탭 하나를 교체한다.
|
||||
- 부모가 있는 지반침하 연계 관로는 비활성 상태에서도 `←`를 항상 표시해 일반 관로와 구분되도록 후속 보완했다.
|
||||
- 탭 최대 5개 초과 시 가장 먼저 열린 탭을 제거하는 FIFO를 유지하고, 제거 대상이 부모 지반침하이면 그 부모를 참조하는 연계 관로 탭도 함께 제거하도록 공통 그룹 제거 함수를 적용했다. 연계 관로 단독 제거는 부모에 영향을 주지 않는다.
|
||||
- BE API·DTO·Repository·DB 변경은 없다.
|
||||
|
||||
Reference in New Issue
Block a user