refactor(B07): 상세 설계 화면 700줄 초과 분리 — 좌측 패널
732줄 한 파일을 둘로 나눔 (동작 불변, 순수 이동). - `B07_DesignDetail_UI_Page.ts` 517줄 — CAD 임베드·postMessage 교신·저장/확정 흐름 - `B07_DesignDetail_UI_Panels.ts` 234줄 — 도면 목록 12분류(`DRAWING_GROUPS`)와 측점 설계 정보 카드. DOM 만 만들고 고르기 동작은 콜백으로 받음 검증: 공용 브라우저 B07 화면 실측 — 도면 목록 패널 1개·항목 54개(「설계 도면 49건」), CAD iframe 1개 로드, 콘솔 오류 0. `tsc --noEmit` 통과, tmp/tests 378 passed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,6 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import "./B07_DesignDetail_UI_Style.css";
|
||||
import { attachCollapsible } from "@ui/ui_template_collapsible";
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import {
|
||||
createButton,
|
||||
@@ -21,6 +20,12 @@ import {
|
||||
} from "@ui/ui_template_elements";
|
||||
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
|
||||
import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend";
|
||||
// 좌측 패널(도면 목록·설계 정보)은 파일이 700줄을 넘어 떼어냈다(2026-09-04).
|
||||
import {
|
||||
buildDesignInfoPanel,
|
||||
buildDrawingSidePanel,
|
||||
isCrossSheet,
|
||||
} from "./B07_DesignDetail_UI_Panels";
|
||||
import { workflowSteps } from "../A00_Common/b_page_scaffold";
|
||||
import {
|
||||
fetchWorkflowState,
|
||||
@@ -34,7 +39,6 @@ import {
|
||||
fetchDesignDrawingList,
|
||||
invalidateDesignDrawing,
|
||||
type CadDrawing,
|
||||
type CrossDesignInfo,
|
||||
type DesignDrawingItem,
|
||||
type DesignDrawingResponse,
|
||||
type QuantityTable,
|
||||
@@ -83,227 +87,8 @@ const CAD_NAVIGATE_MESSAGE = "aislo:b08:navigate";
|
||||
const CAD_TOAST_MESSAGE = "aislo:b08:toast";
|
||||
const CAD_TOAST_ACTION_MESSAGE = "aislo:b08:toast-action";
|
||||
|
||||
/**
|
||||
* 도면 구성 12분류 (2026-08-29 사용자 확정 순서).
|
||||
*
|
||||
* 아직 내용을 만들지 않은 도면은 `blankId`로 서버의 빈 도각 도면에 물린다
|
||||
* (2026-09-01 사용자 지시) — 눌리지 않는 회색 버튼으로 두면 고장난 것처럼 보인다.
|
||||
*/
|
||||
const DRAWING_GROUPS: readonly {
|
||||
label: string;
|
||||
kind?: DesignDrawingItem["kind"];
|
||||
blankId?: string;
|
||||
}[] = [
|
||||
{ label: "표지", kind: "cover" },
|
||||
{ label: "계획평면도(지형)", blankId: "blank_plan_terrain" },
|
||||
{ label: "계획평면도(노선배치도)", blankId: "blank_plan_route" },
|
||||
{ label: "계획평면도(배치도)", blankId: "blank_plan_layout" },
|
||||
{ label: "계획평면도(라이다)", blankId: "blank_plan_lidar" },
|
||||
{ label: "종단면도", kind: "longitudinal" },
|
||||
{ label: "표준 횡단면도", blankId: "blank_cross_standard" },
|
||||
{ label: "횡단면도", kind: "cross" },
|
||||
{ label: "토적도(유토곡선)", kind: "mass_haul" },
|
||||
{ label: "유역도(배수 유역도)", kind: "watershed" },
|
||||
{ label: "표준도", blankId: "blank_standard" },
|
||||
{ label: "용지도", blankId: "blank_landuse" },
|
||||
];
|
||||
|
||||
/** B06 확정 산출물 기반 도면 목록 패널. */
|
||||
function buildDrawingSidePanel(
|
||||
drawings: DesignDrawingItem[],
|
||||
onSelect: (drawing: DesignDrawingItem) => void,
|
||||
errorMessage?: string,
|
||||
): HTMLDivElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "b07-drawing-list";
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "b07-drawing-list__heading";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = "설계 도면";
|
||||
const count = document.createElement("span");
|
||||
count.textContent = `${drawings.length}건`;
|
||||
heading.append(title, count);
|
||||
panel.append(heading);
|
||||
|
||||
if (errorMessage || drawings.length === 0) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "b07-drawing-list__empty";
|
||||
empty.textContent = errorMessage ?? "확정된 종·횡단 도면이 없습니다.";
|
||||
panel.append(empty);
|
||||
return panel;
|
||||
}
|
||||
|
||||
const drawingButton = (drawing: DesignDrawingItem, label: string): HTMLButtonElement => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "b07-drawing-button";
|
||||
button.dataset.drawingId = drawing.id;
|
||||
button.dataset.confirmed = String(drawing.confirmed);
|
||||
const name = document.createElement("span");
|
||||
name.className = "b07-drawing-button__name";
|
||||
name.textContent = label;
|
||||
button.append(name);
|
||||
button.addEventListener("click", () => onSelect(drawing));
|
||||
return button;
|
||||
};
|
||||
|
||||
for (const group of DRAWING_GROUPS) {
|
||||
const items = group.kind
|
||||
? drawings.filter((item) => item.kind === group.kind)
|
||||
: drawings.filter((item) => item.id === group.blankId);
|
||||
// 한 장짜리(와 아직 내용이 없는 도면)는 컨테이너 없이 버튼 하나로 둔다.
|
||||
if (items.length <= 1) {
|
||||
const [drawing] = items;
|
||||
// 서버가 빈 도각을 내주지 못한 경우에만 회색 버튼으로 남는다.
|
||||
if (!drawing) {
|
||||
const placeholder = document.createElement("button");
|
||||
placeholder.type = "button";
|
||||
placeholder.className = "b07-drawing-button";
|
||||
placeholder.disabled = true;
|
||||
placeholder.title = "준비 중";
|
||||
placeholder.dataset.pending = "true";
|
||||
const name = document.createElement("span");
|
||||
name.className = "b07-drawing-button__name";
|
||||
name.textContent = group.label;
|
||||
placeholder.append(name);
|
||||
panel.append(placeholder);
|
||||
continue;
|
||||
}
|
||||
const button = drawingButton(drawing, group.label);
|
||||
// 도각만 있는 도면은 그렇다고 알린다 — 빈 화면을 보고 오류로 오해하지 않게.
|
||||
button.dataset.pending = String(drawing.kind === "blank");
|
||||
if (drawing.kind === "blank") button.title = "준비 중 — 도각만 표시합니다";
|
||||
panel.append(button);
|
||||
continue;
|
||||
}
|
||||
const section = document.createElement("section");
|
||||
// ui-sidebar-section: 사이드 컨테이너 공통 외곽선(B04~B06과 통일, 2026-08-06 사용자 지시).
|
||||
section.className = "b07-drawing-group ui-collapsible ui-sidebar-section";
|
||||
if (group.kind) section.dataset.kind = group.kind;
|
||||
const sectionTitle = document.createElement("h3");
|
||||
sectionTitle.className = "ui-collapsible__title";
|
||||
sectionTitle.textContent = `${group.label} ${items.length}`;
|
||||
section.append(sectionTitle);
|
||||
for (const drawing of items) {
|
||||
// 횡단면도는 장 단위(여러 측점)라 서버가 준 "N장 (구간)" 라벨을 그대로 쓴다.
|
||||
section.append(drawingButton(drawing, drawing.label));
|
||||
}
|
||||
panel.append(section);
|
||||
}
|
||||
attachCollapsible(panel);
|
||||
return panel;
|
||||
}
|
||||
|
||||
const GROUND_TYPE_LABEL: Record<CrossDesignInfo["ground_type"], keyof typeof ui_locales> = {
|
||||
soil: "B06_Design_Ground_Soil",
|
||||
ripping_rock: "B06_Design_Ground_Ripping",
|
||||
blasting_rock: "B06_Design_Ground_Blasting",
|
||||
};
|
||||
|
||||
/** 단면유형에서 절토측 표기를 유도한다. */
|
||||
function cutSideLabel(mode: CrossDesignInfo["section_mode"]): string {
|
||||
if (mode === "left_cut") return L("B06_Design_Ditch_Left");
|
||||
if (mode === "right_cut") return L("B06_Design_Ditch_Right");
|
||||
if (mode === "both_cut") return L("B06_Design_Mode_BothCut");
|
||||
return L("B06_Design_Mode_BothFill");
|
||||
}
|
||||
|
||||
/** 장(여러 측점을 담은 횡단 도면)인가 — id 가 `cross_s…` 면 장이다. */
|
||||
function isCrossSheet(drawing: DesignDrawingItem): boolean {
|
||||
return drawing.kind === "cross" && drawing.id.startsWith("cross_s");
|
||||
}
|
||||
|
||||
/** 측구 규격 표시 문자열 (design 신구조: 형식별 ditch spec, F-2 호환). */
|
||||
function ditchLabel(design: CrossDesignInfo): string {
|
||||
const ditch = design.ditch;
|
||||
if (!ditch || ditch.type === "none" || design.ditch_enabled === false) return "없음";
|
||||
if (ditch.type === "l_type")
|
||||
return `L형 ${ditch.width_m.toFixed(2)}×${ditch.depth_m.toFixed(2)}m`;
|
||||
return `${ditch.top_width_m.toFixed(2)}×${ditch.depth_m.toFixed(2)}m`;
|
||||
}
|
||||
|
||||
function infoRow(label: string, value: string): HTMLElement {
|
||||
const row = document.createElement("div");
|
||||
row.className = "b07-info__row";
|
||||
const key = document.createElement("span");
|
||||
key.className = "b07-info__key";
|
||||
key.textContent = label;
|
||||
const val = document.createElement("span");
|
||||
val.className = "b07-info__val";
|
||||
val.textContent = value;
|
||||
row.append(key, val);
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택 횡단도의 지반정보/계획정보를 2분할로 렌더한다 (잠정치, B07 확정 시 재계산).
|
||||
*
|
||||
* **장(여러 측점을 담은 도면)에는 측점 단위 값이 없다** — 서버가 `design` 을 넘기지
|
||||
* 않는데도 제목만 「측점 …」으로 달려 어느 측점 값인지 오해됐다(2026-09-03 정리).
|
||||
* 장이면 제목을 「장」으로 바꾸고 어디서 봐야 하는지 한 줄로 알린다.
|
||||
*/
|
||||
function buildDesignInfoPanel(
|
||||
title: string,
|
||||
design: CrossDesignInfo | null,
|
||||
scope: "station" | "sheet" = "station",
|
||||
): HTMLElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "b07-info";
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "b07-info__heading";
|
||||
const stationName = document.createElement("strong");
|
||||
const scopeLabel = scope === "sheet" ? L("B07_Info_Sheet") : L("B07_Info_Station");
|
||||
stationName.textContent = `${scopeLabel} ${title}`;
|
||||
const confirmed = design?.status === "confirmed";
|
||||
const badge = document.createElement("span");
|
||||
badge.className = `b07-info__badge${confirmed ? " b07-info__badge--confirmed" : ""}`;
|
||||
badge.textContent = confirmed ? L("B07_Info_Confirmed") : L("B07_Info_Provisional");
|
||||
heading.append(stationName, badge);
|
||||
panel.append(heading);
|
||||
|
||||
if (!design) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "b07-info__empty";
|
||||
empty.textContent = scope === "sheet" ? L("B07_Info_SheetHint") : L("B07_Info_NoDesign");
|
||||
panel.append(empty);
|
||||
return panel;
|
||||
}
|
||||
|
||||
const ground = document.createElement("section");
|
||||
ground.className = "b07-info__block";
|
||||
const groundTitle = document.createElement("h4");
|
||||
groundTitle.textContent = L("B07_Info_Ground_Title");
|
||||
ground.append(
|
||||
groundTitle,
|
||||
infoRow(L("B07_Info_GroundType"), L(GROUND_TYPE_LABEL[design.ground_type])),
|
||||
infoRow(L("B07_Info_CutSide"), cutSideLabel(design.section_mode)),
|
||||
infoRow(
|
||||
L("B07_Info_DitchSide"),
|
||||
design.ditch_side === "left" ? L("B06_Design_Ditch_Left") : L("B06_Design_Ditch_Right"),
|
||||
),
|
||||
);
|
||||
|
||||
const plan = document.createElement("section");
|
||||
plan.className = "b07-info__block";
|
||||
const planTitle = document.createElement("h4");
|
||||
planTitle.textContent = L("B07_Info_Plan_Title");
|
||||
plan.append(
|
||||
planTitle,
|
||||
infoRow(L("B07_Info_DesignElevation"), `${design.design_elevation_m.toFixed(2)}m`),
|
||||
infoRow(L("B07_Info_CutSlope"), `1:${design.cut_slope_ratio}`),
|
||||
infoRow(L("B07_Info_FillSlope"), `1:${design.fill_slope_ratio}`),
|
||||
infoRow(L("B07_Info_RoadWidth"), `${design.roadbed_width_m.toFixed(2)}m`),
|
||||
infoRow(L("B07_Info_Ditch"), ditchLabel(design)),
|
||||
infoRow(L("B07_Info_CutArea"), `${design.cut_area_m2.toFixed(2)}㎡`),
|
||||
infoRow(L("B07_Info_FillArea"), `${design.fill_area_m2.toFixed(2)}㎡`),
|
||||
);
|
||||
|
||||
panel.append(ground, plan);
|
||||
return panel;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* 페이지 진입점
|
||||
* 화면 조립
|
||||
* -------------------------------------------------------------------------- */
|
||||
export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
/* =============================================================================
|
||||
* B07_DesignDetail_UI_Panels.ts
|
||||
* 상세 설계 화면의 좌측 패널 — 도면 목록 12분류, 측점 설계 정보 카드.
|
||||
*
|
||||
* 화면 조립(`B07_DesignDetail_UI_Page.ts`)이 700줄을 넘어 떼어냈다(2026-09-04).
|
||||
* DOM 만 만들고 상태는 갖지 않는다 — 고르기 동작은 인자로 받은 콜백에 넘긴다.
|
||||
* ========================================================================== */
|
||||
|
||||
import { attachCollapsible } from "@ui/ui_template_collapsible";
|
||||
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
||||
import type { CrossDesignInfo, DesignDrawingItem } from "./B07_DesignDetail_Api_Fetch";
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* 도면 구성 12분류 (2026-08-29 사용자 확정 순서).
|
||||
*
|
||||
* 아직 내용을 만들지 않은 도면은 `blankId`로 서버의 빈 도각 도면에 물린다
|
||||
* (2026-09-01 사용자 지시) — 눌리지 않는 회색 버튼으로 두면 고장난 것처럼 보인다.
|
||||
*/
|
||||
export const DRAWING_GROUPS: readonly {
|
||||
label: string;
|
||||
kind?: DesignDrawingItem["kind"];
|
||||
blankId?: string;
|
||||
}[] = [
|
||||
{ label: "표지", kind: "cover" },
|
||||
{ label: "계획평면도(지형)", blankId: "blank_plan_terrain" },
|
||||
{ label: "계획평면도(노선배치도)", blankId: "blank_plan_route" },
|
||||
{ label: "계획평면도(배치도)", blankId: "blank_plan_layout" },
|
||||
{ label: "계획평면도(라이다)", blankId: "blank_plan_lidar" },
|
||||
{ label: "종단면도", kind: "longitudinal" },
|
||||
{ label: "표준 횡단면도", blankId: "blank_cross_standard" },
|
||||
{ label: "횡단면도", kind: "cross" },
|
||||
{ label: "토적도(유토곡선)", kind: "mass_haul" },
|
||||
{ label: "유역도(배수 유역도)", kind: "watershed" },
|
||||
{ label: "표준도", blankId: "blank_standard" },
|
||||
{ label: "용지도", blankId: "blank_landuse" },
|
||||
];
|
||||
|
||||
/** B06 확정 산출물 기반 도면 목록 패널. */
|
||||
export function buildDrawingSidePanel(
|
||||
drawings: DesignDrawingItem[],
|
||||
onSelect: (drawing: DesignDrawingItem) => void,
|
||||
errorMessage?: string,
|
||||
): HTMLDivElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "b07-drawing-list";
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "b07-drawing-list__heading";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = "설계 도면";
|
||||
const count = document.createElement("span");
|
||||
count.textContent = `${drawings.length}건`;
|
||||
heading.append(title, count);
|
||||
panel.append(heading);
|
||||
|
||||
if (errorMessage || drawings.length === 0) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "b07-drawing-list__empty";
|
||||
empty.textContent = errorMessage ?? "확정된 종·횡단 도면이 없습니다.";
|
||||
panel.append(empty);
|
||||
return panel;
|
||||
}
|
||||
|
||||
const drawingButton = (drawing: DesignDrawingItem, label: string): HTMLButtonElement => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "b07-drawing-button";
|
||||
button.dataset.drawingId = drawing.id;
|
||||
button.dataset.confirmed = String(drawing.confirmed);
|
||||
const name = document.createElement("span");
|
||||
name.className = "b07-drawing-button__name";
|
||||
name.textContent = label;
|
||||
button.append(name);
|
||||
button.addEventListener("click", () => onSelect(drawing));
|
||||
return button;
|
||||
};
|
||||
|
||||
for (const group of DRAWING_GROUPS) {
|
||||
const items = group.kind
|
||||
? drawings.filter((item) => item.kind === group.kind)
|
||||
: drawings.filter((item) => item.id === group.blankId);
|
||||
// 한 장짜리(와 아직 내용이 없는 도면)는 컨테이너 없이 버튼 하나로 둔다.
|
||||
if (items.length <= 1) {
|
||||
const [drawing] = items;
|
||||
// 서버가 빈 도각을 내주지 못한 경우에만 회색 버튼으로 남는다.
|
||||
if (!drawing) {
|
||||
const placeholder = document.createElement("button");
|
||||
placeholder.type = "button";
|
||||
placeholder.className = "b07-drawing-button";
|
||||
placeholder.disabled = true;
|
||||
placeholder.title = "준비 중";
|
||||
placeholder.dataset.pending = "true";
|
||||
const name = document.createElement("span");
|
||||
name.className = "b07-drawing-button__name";
|
||||
name.textContent = group.label;
|
||||
placeholder.append(name);
|
||||
panel.append(placeholder);
|
||||
continue;
|
||||
}
|
||||
const button = drawingButton(drawing, group.label);
|
||||
// 도각만 있는 도면은 그렇다고 알린다 — 빈 화면을 보고 오류로 오해하지 않게.
|
||||
button.dataset.pending = String(drawing.kind === "blank");
|
||||
if (drawing.kind === "blank") button.title = "준비 중 — 도각만 표시합니다";
|
||||
panel.append(button);
|
||||
continue;
|
||||
}
|
||||
const section = document.createElement("section");
|
||||
// ui-sidebar-section: 사이드 컨테이너 공통 외곽선(B04~B06과 통일, 2026-08-06 사용자 지시).
|
||||
section.className = "b07-drawing-group ui-collapsible ui-sidebar-section";
|
||||
if (group.kind) section.dataset.kind = group.kind;
|
||||
const sectionTitle = document.createElement("h3");
|
||||
sectionTitle.className = "ui-collapsible__title";
|
||||
sectionTitle.textContent = `${group.label} ${items.length}`;
|
||||
section.append(sectionTitle);
|
||||
for (const drawing of items) {
|
||||
// 횡단면도는 장 단위(여러 측점)라 서버가 준 "N장 (구간)" 라벨을 그대로 쓴다.
|
||||
section.append(drawingButton(drawing, drawing.label));
|
||||
}
|
||||
panel.append(section);
|
||||
}
|
||||
attachCollapsible(panel);
|
||||
return panel;
|
||||
}
|
||||
|
||||
const GROUND_TYPE_LABEL: Record<CrossDesignInfo["ground_type"], keyof typeof ui_locales> = {
|
||||
soil: "B06_Design_Ground_Soil",
|
||||
ripping_rock: "B06_Design_Ground_Ripping",
|
||||
blasting_rock: "B06_Design_Ground_Blasting",
|
||||
};
|
||||
|
||||
/** 단면유형에서 절토측 표기를 유도한다. */
|
||||
function cutSideLabel(mode: CrossDesignInfo["section_mode"]): string {
|
||||
if (mode === "left_cut") return L("B06_Design_Ditch_Left");
|
||||
if (mode === "right_cut") return L("B06_Design_Ditch_Right");
|
||||
if (mode === "both_cut") return L("B06_Design_Mode_BothCut");
|
||||
return L("B06_Design_Mode_BothFill");
|
||||
}
|
||||
|
||||
/** 장(여러 측점을 담은 횡단 도면)인가 — id 가 `cross_s…` 면 장이다. */
|
||||
export function isCrossSheet(drawing: DesignDrawingItem): boolean {
|
||||
return drawing.kind === "cross" && drawing.id.startsWith("cross_s");
|
||||
}
|
||||
|
||||
/** 측구 규격 표시 문자열 (design 신구조: 형식별 ditch spec, F-2 호환). */
|
||||
function ditchLabel(design: CrossDesignInfo): string {
|
||||
const ditch = design.ditch;
|
||||
if (!ditch || ditch.type === "none" || design.ditch_enabled === false) return "없음";
|
||||
if (ditch.type === "l_type")
|
||||
return `L형 ${ditch.width_m.toFixed(2)}×${ditch.depth_m.toFixed(2)}m`;
|
||||
return `${ditch.top_width_m.toFixed(2)}×${ditch.depth_m.toFixed(2)}m`;
|
||||
}
|
||||
|
||||
function infoRow(label: string, value: string): HTMLElement {
|
||||
const row = document.createElement("div");
|
||||
row.className = "b07-info__row";
|
||||
const key = document.createElement("span");
|
||||
key.className = "b07-info__key";
|
||||
key.textContent = label;
|
||||
const val = document.createElement("span");
|
||||
val.className = "b07-info__val";
|
||||
val.textContent = value;
|
||||
row.append(key, val);
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택 횡단도의 지반정보/계획정보를 2분할로 렌더한다 (잠정치, B07 확정 시 재계산).
|
||||
*
|
||||
* **장(여러 측점을 담은 도면)에는 측점 단위 값이 없다** — 서버가 `design` 을 넘기지
|
||||
* 않는데도 제목만 「측점 …」으로 달려 어느 측점 값인지 오해됐다(2026-09-03 정리).
|
||||
* 장이면 제목을 「장」으로 바꾸고 어디서 봐야 하는지 한 줄로 알린다.
|
||||
*/
|
||||
export function buildDesignInfoPanel(
|
||||
title: string,
|
||||
design: CrossDesignInfo | null,
|
||||
scope: "station" | "sheet" = "station",
|
||||
): HTMLElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "b07-info";
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "b07-info__heading";
|
||||
const stationName = document.createElement("strong");
|
||||
const scopeLabel = scope === "sheet" ? L("B07_Info_Sheet") : L("B07_Info_Station");
|
||||
stationName.textContent = `${scopeLabel} ${title}`;
|
||||
const confirmed = design?.status === "confirmed";
|
||||
const badge = document.createElement("span");
|
||||
badge.className = `b07-info__badge${confirmed ? " b07-info__badge--confirmed" : ""}`;
|
||||
badge.textContent = confirmed ? L("B07_Info_Confirmed") : L("B07_Info_Provisional");
|
||||
heading.append(stationName, badge);
|
||||
panel.append(heading);
|
||||
|
||||
if (!design) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "b07-info__empty";
|
||||
empty.textContent = scope === "sheet" ? L("B07_Info_SheetHint") : L("B07_Info_NoDesign");
|
||||
panel.append(empty);
|
||||
return panel;
|
||||
}
|
||||
|
||||
const ground = document.createElement("section");
|
||||
ground.className = "b07-info__block";
|
||||
const groundTitle = document.createElement("h4");
|
||||
groundTitle.textContent = L("B07_Info_Ground_Title");
|
||||
ground.append(
|
||||
groundTitle,
|
||||
infoRow(L("B07_Info_GroundType"), L(GROUND_TYPE_LABEL[design.ground_type])),
|
||||
infoRow(L("B07_Info_CutSide"), cutSideLabel(design.section_mode)),
|
||||
infoRow(
|
||||
L("B07_Info_DitchSide"),
|
||||
design.ditch_side === "left" ? L("B06_Design_Ditch_Left") : L("B06_Design_Ditch_Right"),
|
||||
),
|
||||
);
|
||||
|
||||
const plan = document.createElement("section");
|
||||
plan.className = "b07-info__block";
|
||||
const planTitle = document.createElement("h4");
|
||||
planTitle.textContent = L("B07_Info_Plan_Title");
|
||||
plan.append(
|
||||
planTitle,
|
||||
infoRow(L("B07_Info_DesignElevation"), `${design.design_elevation_m.toFixed(2)}m`),
|
||||
infoRow(L("B07_Info_CutSlope"), `1:${design.cut_slope_ratio}`),
|
||||
infoRow(L("B07_Info_FillSlope"), `1:${design.fill_slope_ratio}`),
|
||||
infoRow(L("B07_Info_RoadWidth"), `${design.roadbed_width_m.toFixed(2)}m`),
|
||||
infoRow(L("B07_Info_Ditch"), ditchLabel(design)),
|
||||
infoRow(L("B07_Info_CutArea"), `${design.cut_area_m2.toFixed(2)}㎡`),
|
||||
infoRow(L("B07_Info_FillArea"), `${design.fill_area_m2.toFixed(2)}㎡`),
|
||||
);
|
||||
|
||||
panel.append(ground, plan);
|
||||
return panel;
|
||||
}
|
||||
Reference in New Issue
Block a user