/* ============================================================================= * B06_Section_UI_Cross_Card_Chrome.ts * 횡단 카드의 **껍데기**(제목행·하단 정보행)만 떼어 낸 조립기 (2026-09-03 · 700줄 제한). * * 카드 본체(`_UI_Cross_View`)는 SVG 도면과 조작을 맡고, 도면과 무관한 머리·꼬리는 여기서 * 만든다. 여기 있는 것은 전부 **읽어서 붙이는 값**이라 카드 상태(줌·선택)를 건드리지 않는다. * ========================================================================== */ import type { CrossSection } from "./B06_Section_Api_Fetch"; import type { RockBoundaryControl } from "./B06_Section_UI_Cross_Design"; import { buildCutSlopeControl, type CutSlopeControl } from "./B06_Section_UI_Cross_CutSlope"; import { buildDesignControls, buildRockBoundaryControl, sectionModeLabel, } from "./B06_Section_UI_Cross_Design"; import { type FillSlopeLength, fillSlopeLengths } from "./B06_Section_UI_Cross_Fit"; import { type DesignChangeHandler, L, stationLabel, structureDisplayName, } from "./B06_Section_UI_Section_Common"; /** * 성토사면 경사길이 표기 칸 — 성토측이 없으면 null. * * 값은 **구조물이 끊기 전 본래 사면**(정본 설계선 기준)이라, 기슭막이·집수정이 선 측점도 * 표준 횡단의 사면 길이를 그대로 보여 준다(2026-09-03 사용자 지시). */ function fillSlopeInfo(section: CrossSection): HTMLElement | null { const lengths = fillSlopeLengths(section); const sides = (["left", "right"] as const).filter((side) => lengths[side] !== null); if (!sides.length) return null; const both = sides.length > 1; const info = document.createElement("span"); info.className = "b06-cross-card__fillslope"; info.textContent = `${L("B06_Cross_FillSlope")} ${sides .map((side) => { const length = lengths[side] as FillSlopeLength; // 계산 반폭 안에서 원지반을 못 만난 사면은 거기까지만 잰 하한값이라 「≥」로 구분한다. const value = `${length.open ? "≥" : ""}${length.lengthM.toFixed(2)}m`; if (!both) return value; const label = L(side === "left" ? "B06_Design_Ditch_Left" : "B06_Design_Ditch_Right"); return `${label} ${value}`; }) .join(" · ")}`; info.title = L("B06_Cross_FillSlope_Tip"); return info; } /** * 카드 제목행을 만들어 카드에 붙인다(설계 조작이 있으면 조작 바까지). * * 1행 구조(E-2/E-3): 측점 위치표기 → 지반유형 → 단면유형 pill → 구조물 → 측점정보. */ export function appendCardHeader( card: HTMLElement, section: CrossSection, stationInterval: number, onDesignChange?: DesignChangeHandler, ): void { const header = document.createElement("header"); const title = document.createElement("div"); title.className = "b06-cross-card__title"; const label = document.createElement("strong"); label.textContent = stationLabel(section.chainage_m, stationInterval); const chainage = document.createElement("span"); chainage.textContent = `${section.chainage_m.toFixed(1)}m`; title.append(label, chainage); // 단면유형 pill(자동 판정, 읽기 전용) — 구조물 pill과 동일 표기, 좌측 배치(E-3). const modePill = document.createElement("span"); modePill.className = "b06-cross-card__mode"; modePill.textContent = sectionModeLabel(section.design?.section_mode); modePill.title = L("B06_Design_Mode_Legend"); const meta = document.createElement("div"); meta.className = "b06-cross-card__meta"; // 사면이 계산 반폭 끝까지 원지반을 못 만난 측점 — 면적이 거기서 잘려 유토곡선까지 // 그 값을 쌓는다. 계산은 손대지 않고 사실만 알린다(2026-09-03 사용자 확정). if (section.design?.slope_unclosed) { const openSlope = document.createElement("span"); openSlope.className = "b06-cross-card__warning"; openSlope.textContent = `⚠ ${L("B06_Cross_SlopeUnclosed")}`; openSlope.title = L("B06_Cross_SlopeUnclosed_Tip"); meta.append(openSlope); } const structureName = section.structure; if (structureName) { const structure = document.createElement("span"); structure.className = "b06-cross-card__structure"; // 배수관은 관종·관경(`파형강관 D1200`)이 아니라 표시 이름 하나로 적는다 // (2026-09-03 사용자 지시). 관종·관경은 툴팁에 남긴다. structure.textContent = structureDisplayName(structureName); structure.title = `구조물: ${structureName}`; meta.append(structure); } // 측점 종류는 시·종점(BP/EP)만 적는다 — "일반측점"은 대다수라 정보가 없다(2026-08-02 사용자 지시). if (section.kind === "bp" || section.kind === "ep") { const kind = document.createElement("span"); kind.textContent = L( section.kind === "ep" ? "B06_Profile_View_Kind_EP" : "B06_Profile_View_Kind_BP", ); meta.append(kind); } // 성토사면 경사길이는 **행 우측 끝**(2026-09-03 사용자 지시) — meta 가 제목행의 오른쪽 // 끝이므로 그 마지막 칸에 붙인다. const fillSlope = fillSlopeInfo(section); if (fillSlope) meta.append(fillSlope); // 단면유형 pill은 지반유형 우측·우측 맞춤으로 배치(1번). 좌측 구분선은 지반유형 세그먼트에 준다(3번). modePill.classList.add("b06-cross-card__mode--right"); header.append(title); if (onDesignChange) { const controls = buildDesignControls(section, onDesignChange); controls.groundSegment.classList.add("b06-cross-card__ground"); header.append(controls.groundSegment, modePill, meta); card.append(header, controls.bar); return; } header.append(modePill, meta); card.append(header); } /** 하단 정보행 — 중심고와 (암 지반일 때) 암 경계선·암 절토 경사 제어. */ export function appendCardFooter( card: HTMLElement, section: CrossSection, rockBoundary?: RockBoundaryControl, cutSlope?: CutSlopeControl, ): void { const footer = document.createElement("footer"); const center = document.createElement("span"); center.textContent = `${L("B06_Profile_View_CenterElevation")} ${section.center_z?.toFixed(2) ?? "-"}m`; footer.append(center); // 암 경계선 제어는 하단 정보 행 가운데(2026-08-02) — 암 지반만. if (rockBoundary && section.design?.geometry_preset === "rock") { const rockControl = buildRockBoundaryControl(section, rockBoundary); rockControl.classList.add("b06-cross-card__rockb"); footer.append(rockControl); } // 암 절토 경사(각도)는 암 측점에만 — 토사 측점은 암반이 없어 쓰이지 않는다 // (2026-09-07 사용자 확정). 전체를 바꾸는 자리는 좌측 [표준 횡단면 설정]이다. if (cutSlope && section.design?.geometry_preset === "rock") { const slopeControl = buildCutSlopeControl(section, cutSlope); slopeControl.classList.add("b06-cross-card__cutslope"); footer.append(slopeControl); } card.append(footer); }