From 56ecc088919f64ba4fb383ec9d0dbb912577afa6 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 29 Aug 2026 14:47:44 +0900 Subject: [PATCH] =?UTF-8?q?feat(B06):=20=ED=9A=A1=EB=8B=A8=EB=8F=84?= =?UTF-8?q?=EB=A5=BC=20=EA=B3=A0=EB=A5=B4=EB=A9=B4=20=EA=B7=B8=20=EC=B8=A1?= =?UTF-8?q?=EC=A0=90=20=EA=B5=AC=EC=A1=B0=EB=AC=BC=EC=9D=84=20=EC=A2=8C?= =?UTF-8?q?=EC=B8=A1=20=ED=8F=BC=EC=97=90=20=EC=98=AC=EB=A6=B0=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SectionView.setStationSelectListener 신설 — 카드(측점) 선택 변경 알림 - 좌측 패널 showAtChainage: 계곡 통과 시설(pipe_points) 우선, 없으면 그 측점을 덮는 구조물 정본 항목(점형 근사·구간형 범위), 둘 다 없으면 선택 해제 - 연동 링크 카드는 structureSpan.ownerOf로 소유 측점 시설을 보여 준다 검증(공용 브라우저): 4+4.3 배수관·7+9.7 세월교·10+0.9 BOX암거 카드 클릭 시 종류·기준 측점·목록 강조 일치. 링크 카드 4+0.0→4+4.3, 14+0.0→13+15.7 소유 측점 표시. 구조물 없는 0+0.0 카드는 폼 해제. typecheck 통과. --- B06_Section/B06_Section_UI_Page.ts | 13 +++++++ .../B06_Section_UI_Page_Structures_Panel.ts | 36 +++++++++++++++++-- B06_Section/B06_Section_UI_Section_View.ts | 10 ++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/B06_Section/B06_Section_UI_Page.ts b/B06_Section/B06_Section_UI_Page.ts index 976340fb..c0261fb4 100644 --- a/B06_Section/B06_Section_UI_Page.ts +++ b/B06_Section/B06_Section_UI_Page.ts @@ -466,6 +466,19 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise { ); // 횡단도 벽·구체 선택 → 좌측 「구조물 배치」 폼에 그 시설 로드(2026-08-29 일원화). wireStructureSelection(stationControls, () => sectionDetail, structuresPanel); + // 횡단도(카드) 자체를 골라도 그 측점 구조물 정보를 폼에 올린다(2026-08-29 사용자). + // 연동으로 옆에서 넘어온 카드는 **소유 측점** 시설을 보여 준다. + sectionView.setStationSelectListener((stationId) => { + const target = stationId + ? sectionDetail?.cross_sections.find((section) => section.station_id === stationId) + : null; + if (!target) { + structuresPanel.showAtChainage(null); + return; + } + const owner = stationControls.structureSpan.ownerOf(target); + structuresPanel.showAtChainage(owner?.chainage_m ?? target.chainage_m); + }); // 메인 영역: 종·횡단 도면(sectionView) 또는 안내 메시지를 표시한다. const mainArea = document.createElement("div"); diff --git a/B06_Section/B06_Section_UI_Page_Structures_Panel.ts b/B06_Section/B06_Section_UI_Page_Structures_Panel.ts index 23f4c237..88e7aae5 100644 --- a/B06_Section/B06_Section_UI_Page_Structures_Panel.ts +++ b/B06_Section/B06_Section_UI_Page_Structures_Panel.ts @@ -47,10 +47,25 @@ export interface B06StructuresPanel { load: () => Promise; /** 횡단도에서 고른 벽·구체의 시설을 폼에 올린다(null = 해제). */ showPipeAt: (chainageM: number | null) => void; + /** 고른 횡단도(측점)에 있는 구조물을 폼에 올린다 — 계곡 통과 시설이 먼저고, + * 없으면 그 측점을 덮는 구조물 정본 항목. 둘 다 없으면 해제(2026-08-29). */ + showAtChainage: (chainageM: number | null) => void; +} + +/** 관 매칭 허용 오차(m) — 관은 계획선에 스냅돼 측점과 살짝 어긋난다(B05와 같은 기준). */ +const PIPE_MATCH_M = 0.51; + +/** 이 구조물이 그 측점을 덮는가 — 점형은 근사 일치, 구간형은 시작~종료 안. */ +function coversChainage(structure: StructureInstance, chainageM: number): boolean { + if (structure.placement === "interval" && structure.start_m != null && structure.end_m != null) { + return chainageM >= structure.start_m - 0.01 && chainageM <= structure.end_m + 0.01; + } + return Math.abs(structureAnchorM(structure) - chainageM) < PIPE_MATCH_M; } export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06StructuresPanel { let structures: StructureInstance[] = []; + let pipeFacilities: PipeFacilityItem[] = []; /** 새 항목에 식별자를 미리 붙인다 — 저장 전에도 목록에서 고르고 지울 수 있어야 * 하고, 서버는 빈 값일 때만 새로 발급한다(B05 다리와 같은 규칙). */ @@ -95,7 +110,7 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc // 저장하지 않고 나갔던 조작분이 있으면 그것으로 화면을 세운다(B05와 같은 규칙). structures = readPendingStructures(projectId) ?? stored.structures; section.setStructures(structures); - const pipes: PipeFacilityItem[] = pipeResponse.pipe_points.map((pipe) => ({ + pipeFacilities = pipeResponse.pipe_points.map((pipe) => ({ chainage_m: pipe.chainage_m, facility: pipe.facility ?? "pipe", start_m: pipe.start_m, @@ -104,7 +119,7 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc options: pipe.options, design_flow_m3s: null, })); - section.setPipeFacilities(pipes); + section.setPipeFacilities(pipeFacilities); } catch (error) { showToast( error instanceof Error ? error.message : "구조물 정보를 불러오지 못했습니다.", @@ -118,6 +133,23 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc listRoot: section.listRoot, load, showPipeAt: (chainageM) => section.selectPipeByChainage(chainageM), + showAtChainage: (chainageM) => { + if (chainageM === null) { + section.selectById(null); + return; + } + // 계곡 통과 시설이 먼저다 — 같은 측점에 구조물 정본 항목이 겹쳐도 횡단도에 + // 그려지는 것은 이쪽이다. + const pipe = pipeFacilities.find( + (entry) => Math.abs(entry.chainage_m - chainageM) < PIPE_MATCH_M, + ); + if (pipe) { + section.selectPipeByChainage(pipe.chainage_m); + return; + } + const hit = structures.find((entry) => coversChainage(entry, chainageM)); + section.selectById(hit?.structure_id ?? null); + }, }; } diff --git a/B06_Section/B06_Section_UI_Section_View.ts b/B06_Section/B06_Section_UI_Section_View.ts index fd0cbe01..27064096 100644 --- a/B06_Section/B06_Section_UI_Section_View.ts +++ b/B06_Section/B06_Section_UI_Section_View.ts @@ -115,6 +115,9 @@ export interface SectionViewController { /** 좌측 구조물 목록에서 고른 측점 카드를 선택하고 화면에 드러낸다 — 재클릭 토글 없음 * (2026-08-29 B05/B06 일원화: 목록 클릭 → 해당 카드 스크롤·강조). */ focusStation: (stationId: string) => void; + /** 카드(측점) 선택이 바뀔 때 알림 — 좌측 「구조물 배치」 폼이 그 측점 구조물을 + * 올린다(2026-08-29 일원화). null = 선택 해제. */ + setStationSelectListener: (listener: (stationId: string | null) => void) => void; clear: () => void; dispose: () => void; } @@ -356,10 +359,14 @@ export function createSectionView( * (2026-08-02 사용자 지적). 선택 표시가 바뀌는 카드는 이전·현재 둘뿐이고, 그 둘도 클래스와 * 강조 상태만 갈아 끼운다. */ + /** 카드 선택 변경 수신자(좌측 구조물 폼). 페이지가 등록한다. */ + let stationSelectListener: ((stationId: string | null) => void) | null = null; + const applySelection = (previousStationId: string | null): void => { markCard(previousStationId); if (selectedStationId !== previousStationId) markCard(selectedStationId); drawPanel(); + if (selectedStationId !== previousStationId) stationSelectListener?.(selectedStationId); }; /** 같은 측점을 다시 고르면 선택을 푼다(2026-08-02 사용자 지시) — 카드·측점선 어느 쪽이든. */ @@ -708,6 +715,9 @@ export function createSectionView( if (selectedStationId !== stationId) selectStation(stationId, true); else revealCard(stationId, "smooth"); }, + setStationSelectListener(listener) { + stationSelectListener = listener; + }, clear() { currentDetail = null; selectedStationId = null;