앞 커밋에서 남긴 두 파일을 마저 갈랐다. 상태를 모듈로 옮기면 나머지 참조가 전부
바뀌므로 상태는 제자리에 두고 **접근자만 넘기는** 방식으로 동작·공개 인터페이스를
보존했다.
- Profile_Panel 1152 → 649
- _Profile_Layout: X축 배치(측점 칸 폭·캔버스 폭·chainage↔x 매핑). 순수 함수
- _Profile_Heights: 그래프·유토곡선·테이블 높이 배분. 드래그 플래그·상세 유무는
본체가 계속 들고 접근자로 읽는다(저장 높이 기준 판정 규칙 그대로)
- _Profile_Render: 본문 재구성(캔버스·그래프·구조물 레인·테이블·유토곡선).
그리기 시작 시 상태를 스냅숏하되 이벤트 핸들러 안에서만 현재값을 다시 읽는다
- _Profile_Balance: 상단 균형 표시줄(절·성토·불균형·위반 경고·초기선 복원)
- Page 1031 → 685
- _Page_Helpers: 설계폭 조회·모델 경계 변환·마커 복원·비정규 측점 보간 +
시설 표시 이름
- _Page_Structures: 구조물 정본과 관 지점 정본을 사이드 목록·그래프·3D에 맞추는
다리. 두 정본을 섞는 지점이라 여기만 상태(비정규 측점 목록·판번호·저장 큐·
타입 사전)를 팩토리 안으로 옮겼고, 본체는 bridge.irregularStations()로 읽는다
B05_Profile 전 파일이 700줄 이하가 됐다(최대 695).
검증: npm run typecheck 무오류, npm run build 성공(374 modules),
pytest tmp/tests 107 passed·7 skipped, prettier 정합.
프론트 테스트 러너가 없어 실제 화면 동작 확인은 남는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
95 lines
4.4 KiB
TypeScript
95 lines
4.4 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_Profile_Balance.ts
|
|
* 종단 패널 상단 균형 표시줄 — 절·성토 면적, 불균형률, 변화점·종단곡선 개수,
|
|
* 종단기울기 위반 경고, [초기선 복원] 버튼, 미저장 배지.
|
|
*
|
|
* 패널 본체(B05_Profile_UI_Profile_Panel)가 700줄 한계에 닿아 분리했다.
|
|
* 표시줄은 상태를 갖지 않는다 — 그릴 때마다 현재 선형·편집 상태를 인자로 받는다.
|
|
* ========================================================================== */
|
|
|
|
import type { ProfileAlignment } from "./B05_Profile_UI_Profile_Alignment";
|
|
|
|
export interface BalanceBarParams {
|
|
/** 표시줄 컨테이너. 그릴 때마다 통째로 갈아 끼운다. */
|
|
balanceBar: HTMLElement;
|
|
/** 현재 계획선(없으면 안내만 띄운다). */
|
|
alignment: ProfileAlignment | null;
|
|
/** 계획선이 없고 저장분이 구버전 형식일 때 재계산을 안내한다. */
|
|
legacyAlignment: boolean;
|
|
/** 편집이 있었는지(초기선 복원 버튼 노출 조건). */
|
|
edited: boolean;
|
|
/** 비정규 측점이 있는지(초기선 복원 버튼 노출 조건). */
|
|
hasIrregularStations: boolean;
|
|
/** 저장되지 않은 편집이 있는지. */
|
|
dirty: boolean;
|
|
/** [초기선 복원] — 편집·비정규 측점을 모두 지운다. */
|
|
onResetAll: () => void;
|
|
}
|
|
|
|
export function renderBalanceBar(params: BalanceBarParams): void {
|
|
params.balanceBar.replaceChildren();
|
|
if (!params.alignment) {
|
|
if (params.legacyAlignment) {
|
|
const note = document.createElement("span");
|
|
note.className = "b05-route-profile__balance-warning";
|
|
note.textContent =
|
|
"⚠ 계획선 데이터가 구버전 형식입니다 — [최적 경로 계산]을 다시 실행하세요.";
|
|
params.balanceBar.append(note);
|
|
}
|
|
return;
|
|
}
|
|
const { alignment } = params;
|
|
const { balance, policy, violations } = alignment;
|
|
const entries: Array<[string, string, string?]> = [
|
|
["절토", `${balance.cut_area_m2.toFixed(1)} m²`, "cut"],
|
|
["성토", `${balance.fill_area_m2.toFixed(1)} m²`, "fill"],
|
|
[
|
|
"불균형",
|
|
`${balance.imbalance_percent.toFixed(1)} % / 허용 ${balance.tolerance_percent.toFixed(0)} %`,
|
|
balance.within_tolerance ? undefined : "over",
|
|
],
|
|
["변화점", `${alignment.pvi.length} 개`],
|
|
["종단곡선", `${alignment.curves.filter((curve) => !curve.omitted).length} 개`],
|
|
// 기준은 길이 L이다. 옛 저장분(L 없음)만 그 시절 기준인 R을 그대로 밝혀 적는다.
|
|
Number.isFinite(policy.default_curve_length_m)
|
|
? ["기본 곡선길이 L", `${(policy.default_curve_length_m as number).toFixed(1)} m`]
|
|
: ["기본 R(옛 저장분)", `${policy.default_curve_radius_m.toFixed(1)} m`],
|
|
];
|
|
const editedCount = Object.keys(alignment.edits.station_offsets).length;
|
|
if (editedCount) entries.push(["편집 측점", `${editedCount} 개`, "edited"]);
|
|
entries.forEach(([label, value, tone]) => {
|
|
const item = document.createElement("span");
|
|
item.className = `b05-route-profile__balance-item${tone ? ` is-${tone}` : ""}`;
|
|
const caption = document.createElement("em");
|
|
caption.textContent = label;
|
|
item.append(caption, document.createTextNode(value));
|
|
params.balanceBar.append(item);
|
|
});
|
|
if (violations.length) {
|
|
const warning = document.createElement("span");
|
|
warning.className = "b05-route-profile__balance-warning";
|
|
warning.textContent = `⚠ 종단기울기 초과 ${violations.length}개 구간`;
|
|
warning.title = violations
|
|
.map((item) => `구간 ${item.segment_index + 1}: ${item.value.toFixed(2)}% > ${item.limit}%`)
|
|
.join("\n");
|
|
params.balanceBar.append(warning);
|
|
}
|
|
if (params.edited || params.hasIrregularStations) {
|
|
const reset = document.createElement("button");
|
|
reset.type = "button";
|
|
reset.className = "b05-route-profile__balance-reset";
|
|
reset.textContent = "초기선 복원";
|
|
reset.title = "모든 편집과 추가한 비정규 측점을 지우고 자동 산출된 계획선으로 되돌립니다.";
|
|
reset.addEventListener("click", () => {
|
|
params.onResetAll();
|
|
});
|
|
params.balanceBar.append(reset);
|
|
}
|
|
if (params.dirty) {
|
|
const badge = document.createElement("span");
|
|
badge.className = "b05-route-profile__balance-item is-unsaved";
|
|
badge.textContent = "미저장 (확정 시 반영)";
|
|
params.balanceBar.append(badge);
|
|
}
|
|
}
|