Files
Aislo/old_code/B08_Quantity/B08_Quantity_UI_Side_Gravel.ts
T
eomsangdonandClaude Opus 5 8472fc9f40 refactor(B08,B09): 폴더째 old_code 로 옮기고 빈 화면 둘만 남김 (PLAN 7-3)
B08_Quantity 86 · B09_Estimation 111 파일을 old_code/ 로 옮김(지우지 않음).
화면은 메뉴·주소·단계 막대만 남은 빈 틀 둘 — main.py 라우터 13 개는 끊음.
B07 이 빌려 쓰던 비탈 길이·면적은 필요한 함수만 B07_DesignDetail_Engine_SlopeGeometry
로 옮겨 적음(면적 적분·노면 면적·측점 묶음은 안 옮김) · 시험 하나를 새로 둠.
B07 구조물도 조립(Cad_StandardSheet)은 2026-09-13 에 이미 도면 목록에서 빠져
부르는 곳이 없어 old_code 로 같이 보냄 — 구조물 그림은 되살리지 않음.
B06 구조물 몫 조회는 빈 값으로 두어 화면이 그대로 서게 함.
B08·B09 를 부르던 시험 25 개도 old_code/resources/tester 로 옮김.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
2026-09-22 12:27:45 +09:00

160 lines
5.7 KiB
TypeScript

/* =============================================================================
* B08_Quantity_UI_Side_Gravel.ts
* 산출 조건 「혼합석 부설」 구획 — 범위 고르개 · 연약·습윤 구간 · 두께·C·L (2026-09-15 브레인 포장 둘 ⓑ).
*
* 페이지 본문(`B08_Quantity_UI_Page.ts`)이 700줄을 넘어 새 칸을 이 파일로 뺌(CLAUDE.md 4장).
* ⚠ 화면은 셈하지 않음 — 면적·부피는 서버 `gravel` 을 보이기만 · 제안값도 서버 `gravel.suggested`.
* ⚠ 빈 칸(`null`)은 「제안값으로」 — 0 과 다름. 구간 글자를 못 읽으면 저장값을 안 바꾸고 알림.
* ========================================================================== */
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import type { SideFieldHelpers } from "./B08_Quantity_UI_Side_TreeWaste";
function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
export interface GravelRange {
from_m: number;
to_m: number;
}
export interface GravelDraft {
gravel_all_unpaved: boolean;
gravel_soft_wet_ranges: GravelRange[];
gravel_thickness_m: number | null;
gravel_conversion_c: number | null;
gravel_conversion_l: number | null;
}
/** 서버 `gravel_surfacing` 결과 중 화면이 보이는 몫. */
export interface GravelSummary {
area_m2: number;
loose_m3: number;
excavation_soil_m3: number;
suggested?: { thickness_m: number; c: number; l: number };
}
export function gravelDraftFrom(stored: Record<string, unknown>): GravelDraft {
const ranges = Array.isArray(stored.gravel_soft_wet_ranges)
? (stored.gravel_soft_wet_ranges as GravelRange[])
: [];
const num = (value: unknown) => (typeof value === "number" ? value : null);
return {
gravel_all_unpaved: Boolean(stored.gravel_all_unpaved),
gravel_soft_wet_ranges: ranges.map((r) => ({ from_m: r.from_m, to_m: r.to_m })),
gravel_thickness_m: num(stored.gravel_thickness_m),
gravel_conversion_c: num(stored.gravel_conversion_c),
gravel_conversion_l: num(stored.gravel_conversion_l),
};
}
/** [저장] 몸통 — 구간은 **통째로**(지운 구간까지 가야 되돌릴 길이 있음) · 숫자 `null` 도 보냄. */
export function gravelPayload(draft: GravelDraft): GravelDraft {
return gravelDraftFrom(draft as unknown as Record<string, unknown>);
}
/** 「120~160, 480~520」 → 구간 목록. 한 조각이라도 못 읽으면 `null`(빈 글은 빈 목록). */
export function parseGravelRanges(text: string): GravelRange[] | null {
const parts = text
.split(",")
.map((part) => part.trim())
.filter(Boolean);
const ranges: GravelRange[] = [];
for (const part of parts) {
const match = /^(\d+(?:\.\d+)?)\s*[~\-]\s*(\d+(?:\.\d+)?)$/.exec(part);
if (!match) return null;
const a = Number(match[1]);
const b = Number(match[2]);
if (a === b) return null;
ranges.push({ from_m: Math.min(a, b), to_m: Math.max(a, b) });
}
return ranges;
}
export function appendGravelFields(
panel: HTMLElement,
draft: GravelDraft & { dirty: boolean },
summary: GravelSummary | undefined,
h: SideFieldHelpers,
): void {
panel.append(h.field(L("B08_Quantity_Side_Gravel"), ""));
panel.append(
h.selectField(
L("B08_Quantity_Gravel_Scope"),
draft.gravel_all_unpaved ? "all" : "",
[
{ value: "", label: L("B08_Quantity_Gravel_Scope_Legal") },
{ value: "all", label: L("B08_Quantity_Gravel_Scope_All") },
],
(value) => {
draft.gravel_all_unpaved = value === "all";
draft.dirty = true;
},
),
);
const row = document.createElement("label");
row.className = "b08-quantity__field";
const name = document.createElement("span");
name.textContent = L("B08_Quantity_Gravel_SoftWet");
const input = document.createElement("input");
input.type = "text";
input.className = "b08-quantity__input";
input.placeholder = L("B08_Quantity_Gravel_SoftWet_Placeholder");
input.value = draft.gravel_soft_wet_ranges.map((r) => `${r.from_m}~${r.to_m}`).join(", ");
const warn = h.hintRow("");
input.addEventListener("input", () => {
const parsed = parseGravelRanges(input.value);
warn.textContent = parsed ? "" : L("B08_Quantity_Gravel_SoftWet_Invalid");
if (!parsed) return;
draft.gravel_soft_wet_ranges = parsed;
draft.dirty = true;
});
row.append(name, input);
panel.append(row, warn);
const suggested = summary?.suggested;
const number = (
label: keyof typeof ui_locales,
value: number | null,
hint: number | undefined,
set: (v: number | null) => void,
) => {
const field = h.optionalNumberField(L(label), value, "0.01", (typed) => {
set(typed);
draft.dirty = true;
});
const box = field.querySelector("input");
if (box && hint !== undefined)
box.placeholder = `${L("B08_Quantity_Gravel_Suggested")} ${hint}`;
return field;
};
panel.append(
number(
"B08_Quantity_Gravel_Thickness",
draft.gravel_thickness_m,
suggested?.thickness_m,
(v) => {
draft.gravel_thickness_m = v;
},
),
number("B08_Quantity_Gravel_C", draft.gravel_conversion_c, suggested?.c, (v) => {
draft.gravel_conversion_c = v;
}),
number("B08_Quantity_Gravel_L", draft.gravel_conversion_l, suggested?.l, (v) => {
draft.gravel_conversion_l = v;
}),
);
if (summary) {
const fmt = (value: number) => value.toLocaleString("ko-KR", { maximumFractionDigits: 2 });
panel.append(
h.hintRow(
`${L("B08_Quantity_Gravel_Now")} — ${fmt(summary.area_m2)}㎡ · ${fmt(summary.loose_m3)}㎥` +
` · ${L("B08_Quantity_Gravel_Excavation")} ${fmt(summary.excavation_soil_m3)}㎥`,
),
);
}
panel.append(h.hintRow(L("B08_Quantity_Side_Gravel_Hint")));
}