feat(B06): 노면공 고름 = 범위 카드 테두리 대신 포장층 강조 · 카드 누름으로 노면공 열기 끊음(152-5)

- 포장층 범위 표식(종류 · 시작 · 끝) · 고른 노면공 층만 is-active(겹침은 하나만)
- 다른 구간형은 --span 그대로
- resolveSelection ③ 덮는 구조물에 거르개 · B06 은 G군 뺌 · 덮는 것 없으면 알약 · 강조 풂

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL
This commit is contained in:
2026-10-09 19:13:08 +09:00
co-authored by Claude Opus 5.5
parent 9904d797bb
commit 4ae7ee25cc
9 changed files with 252 additions and 20 deletions
+21 -9
View File
@@ -19,7 +19,7 @@ import type {
HaulEquipmentLimit,
SectionDetailResponse,
} from "./B06_Section_Api_Fetch";
import type { StructureInstance } from "../B05_Profile/B05_Profile_Api_Structures";
import type { StructureInstance, StructureType } from "../B05_Profile/B05_Profile_Api_Structures";
import type { RockBoundaryControl } from "./B06_Section_UI_Cross_Design";
import type { CrossAreaKey } from "./B06_Section_UI_Cross_Areas";
import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
@@ -65,6 +65,7 @@ import {
} from "./B06_Section_UI_Section_Common";
import type { SectionViewController } from "./B06_Section_UI_Section_View_Types";
import { createFillSlopeNotice } from "./B06_Section_UI_Cross_FillSlope_Notice";
import { highlightPavement } from "./B06_Section_UI_Cross_Design_Surface";
import { createSectionProfile } from "./B06_Section_UI_Section_View_Profile";
import type { DesignPageShared } from "../A00_Common/design/design_shared";
@@ -231,23 +232,33 @@ export function createSectionView(
if (revetOffset) revetOffset.onSelectionChange = syncRevetHighlights;
/**
* 고른 구간형 구조물(노면공 등)의 시작~종료 안 횡단 카드 전부에 테두리 강조(106-1).
* 고른 구간형 구조물(옹벽 · 독립 흙막이 · 사토장)의 시작~종료 안 횡단 카드 전부에 테두리 강조(106-1).
* 노면공(G군)은 테두리 대신 범위 카드 안 그 노면공의 포장층만 강조(152-5) — 겹친 노면공은 범위 표식으로 하나만.
* 가장 가까운 한 장의 `--selected` 는 그대로 — 클래스만 갈아 끼워 줌·팬을 지킨다.
*/
let spanMarks: ReadonlyArray<StructureInstance> = [];
let spanTypes: ReadonlyArray<StructureType> = [];
let spanMarkId: string | null = null;
const inSpan = (section: CrossSection): boolean => {
const mark = spanMarkId ? spanMarks.find((entry) => entry.structure_id === spanMarkId) : null;
const inSpan = (section: CrossSection, mark: StructureInstance | undefined): boolean => {
const { start_m: start, end_m: end } = mark ?? {};
if (start == null || end == null || start === end) return false;
const at = section.chainage_m;
return at >= Math.min(start, end) - 0.01 && at <= Math.max(start, end) + 0.01;
};
const applySpan = (card: HTMLElement, section: CrossSection): void => {
const mark = spanMarkId
? spanMarks.find((entry) => entry.structure_id === spanMarkId)
: undefined;
const span = inSpan(section, mark);
const pave = span && spanTypes.find((type) => type.type_id === mark?.type_id)?.group === "G";
card.classList.toggle("b06-cross-card--span", span && !pave);
highlightPavement(card, pave ? mark : undefined);
};
const syncSpanHighlights = (): void => {
for (const section of currentDetail?.cross_sections ?? [])
document
.getElementById(`cross-${section.station_id}`)
?.classList.toggle("b06-cross-card--span", inSpan(section));
for (const section of currentDetail?.cross_sections ?? []) {
const card = document.getElementById(`cross-${section.station_id}`);
if (card) applySpan(card, section);
}
};
/** 카드 선택 변경 수신자(좌측 구조물 폼). 페이지가 등록한다. */
@@ -349,7 +360,7 @@ export function createSectionView(
plotBase,
cutSlope,
);
card.classList.toggle("b06-cross-card--span", inSpan(section));
applySpan(card, section);
return card;
};
@@ -543,6 +554,7 @@ export function createSectionView(
},
setStructureMarks(structures, types) {
spanMarks = structures;
spanTypes = types;
syncSpanHighlights();
profile.setMarks(structures, types);
// 검증용 훅 — 알약 레인이 무엇을 받았는지 화면 밖에서 수치로 본다