Files
Aislo/B06_Section/B06_Section_UI_Page_Structure_Pick.ts
T
eomsangdonandClaude Sonnet 5.5 8a9456579a refactor(B05·B06): 측점 · 구조물 고름을 selection 한 곳으로 · 관 찾는 거리 PIPE_MATCH_M 한 값 · 횡단 카드 누름이 옹벽 · 세월교 · BOX · 기슭막이 폼을 엶 (24-2d)
- 목록 · 종단 알약 · 3D · 횡단 카드 · 벽 어디서 골라도 selectDesign 한 길로 세션에 적음(구조물 id 포함) — B05 목록 구조물 고름도 B06 으로 이어짐
- 폼이 열 항목 풀이 resolveSelection 한 곳(관 먼저 · 구조물 다음 · 가장 가까운 것) — 공용 폼의 0.05 없앰
- B06 벽 · 구체 누름은 showAtChainage 하나를 탐(옛 관 전용 showPipeAt 없앰) · 벽 누름 중 폼이 카드를 먼저 고르는 이중 토글 막음
- B06 진입 때 구조물 id 로 카드를 찾고, 못 찾으면 다시 부르게 함 · B05 진입 때 구조물 id 가 있을 때만 구조물로 연다
- 시험 test_b05_b06_selection_single 21

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ly2oHAaTDYRjcniAmHR81z
2026-09-29 21:02:55 +09:00

104 lines
5.2 KiB
TypeScript

/* =============================================================================
* B06_Section_UI_Page_Structure_Pick.ts
* B05 3D에서 고른 구조물을 **B06 화면에 얹는다**(2026-09-04 사용자 확정) — 그 측점
* 카드를 고르고 화면에 드러낸 뒤, 부재키가 있으면 그 시설의 조정창까지 연다.
*
* 부재키는 **횡단도가 그 부재를 부르는 이름 그대로**다. 이름이 겹쳐도(배수관 유입 ↔
* 세월교 유입) 한 측점에는 시설이 하나뿐이라 헷갈리지 않는다 — 그 측점이 세월교면
* 세월교 조정창, BOX암거면 BOX 조정창, 아니면 기슭막이 조정창으로 보낸다.
* 본체(`body` — 세월교 월류부 상판·BOX 구체)는 고르는 부재가 아니라 카드까지만 연다.
*
* 세월교·BOX는 조정창 하나가 그 측 부재 여럿(벽·바닥·날개벽·에이프런)을 함께 다루므로
* 부재키가 `측이름:부재`(예 `inlet:wall`) 꼴이다 — **앞칸이 조정창 이름**이다. 3D 강조는
* 부재 하나만 켜야 해서 뒤칸까지 나눠 두었다(2026-09-04 사용자 확정).
* ========================================================================== */
import type { SectionDetailResponse } from "./B06_Section_Api_Fetch";
import type { RevetKey } from "./B06_Section_UI_Cross_Culvert";
import type { StructurePickHandoff } from "../B05_Profile/B05_Profile_UI_Structure_Pick_Session";
import {
coversChainage,
isNear,
type SelectableStructure,
} from "../A00_Common/design/design_selection";
/** 기슭막이 조정창이 받는 부재키 — 집수정·배관·본체는 여기서 빠진다. */
const REVET_KEYS = /^(inlet|outlet|own|extra\d+|bextra\d+)$/;
interface PickTargets {
focusStation: (stationId: string) => void;
refreshCard: (chainageM: number) => void;
revetSelect: (chainageM: number, key: RevetKey) => void;
fordSelect: (chainageM: number, role: "inlet" | "outlet") => void;
boxSelect: (chainageM: number, role: "left" | "right") => void;
}
/**
* 넘김값을 화면에 적용한다. 값이 없거나 그 측점이 이 노선에 없으면 아무것도 안 한다.
* 카드가 다 선 뒤에 고른다 — 스크롤이 자리를 잡아야 한다. 카드를 찾아 적용했으면 true —
* 못 찾았으면(구조물 목록이 아직 안 실린 경우 등) false 라 부르는 쪽이 나중에 다시 부른다.
*/
export function applyStructurePick(
handoff: StructurePickHandoff | null,
detail: SectionDetailResponse | null,
targets: PickTargets,
/** 구조물 정본 — 카드 위치가 아닌 곳(구간형 옹벽 한가운데 등)을 골랐을 때 그 카드를 찾는 데 쓴다. */
structures: ReadonlyArray<SelectableStructure> = [],
): boolean {
if (!handoff) return false;
// 허용오차는 관·구조물 찾는 거리 한 값이다 — 0.01m 로는 3D 가 준 누가거리가
// 소수점 아래에서 조금만 달라도 그 측점을 못 찾고 조용히 끝났다(2026-09-05 진단).
const sections = detail?.cross_sections ?? [];
// 구조물 id 가 있으면 그 구조물의 카드가 먼저다 — 같은 측점 근처에 다른 카드(관)가 있어도.
const byId = handoff.structureId
? structures.find((entry) => entry.structure_id === handoff.structureId)
: undefined;
const covering = structures.find((entry) => coversChainage(entry, handoff.at));
const target =
(byId && cardOfStructure(sections, byId, handoff.at)) ||
sections.find((section) => isNear(section.chainage_m, handoff.at)) ||
(covering && cardOfStructure(sections, covering, handoff.at));
if (!target) return false;
// 조정창은 앞칸(측 이름)만 본다 — 뒤칸은 3D 강조를 부재 하나로 좁히는 몫이다.
const key = handoff.key?.split(":")[0];
requestAnimationFrame(() => {
targets.focusStation(target.station_id);
if (!key || key === "body") return;
if (target.ford) {
if (key !== "inlet" && key !== "outlet") return;
targets.fordSelect(target.chainage_m, key);
} else if (target.box) {
if (key !== "left" && key !== "right") return;
targets.boxSelect(target.chainage_m, key);
} else {
if (!REVET_KEYS.test(key)) return;
targets.revetSelect(target.chainage_m, key as RevetKey);
}
targets.refreshCard(target.chainage_m);
});
return true;
}
type Sections = SectionDetailResponse["cross_sections"];
/** 구조물이 자리한 카드 — 구간형이면 그 안의 첫 카드, 점형이면 가장 가까운 카드. 옹벽 · 기슭막이는
* 횡단 제원이라 시작 측점 카드가 주인이다(관은 `isNear` 로 카드를 바로 찾는다). */
function cardOfStructure(
sections: Sections,
structure: SelectableStructure,
at: number,
): Sections[number] | undefined {
if (!sections.length) return undefined;
const start = structure.start_m ?? structure.chainage_m;
const end = structure.end_m ?? structure.chainage_m;
if (start != null && end != null) {
const inside = sections.find(
(section) => section.chainage_m >= start - 0.01 && section.chainage_m <= end + 0.01,
);
if (inside) return inside;
}
return sections.reduce((best, entry) =>
Math.abs(entry.chainage_m - at) < Math.abs(best.chainage_m - at) ? entry : best,
);
}