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
132 lines
4.5 KiB
TypeScript
132 lines
4.5 KiB
TypeScript
/* =============================================================================
|
||
* B08_Quantity_UI_Side_TreeWaste.ts
|
||
* 산출 조건 「임목폐기물」 구획 — 현장 조사값 넷 · 수동 처리단가 · 분리발주(2026-09-14 판정).
|
||
*
|
||
* 페이지 본문(`B08_Quantity_UI_Page.ts`)이 700줄을 넘어 새 칸을 이 파일로 뺌(CLAUDE.md 4장).
|
||
* ⚠ 톤은 서버가 셈 — 여기는 값만 캐시에 쌓고 [저장]에서 정본으로 감(5장).
|
||
* ========================================================================== */
|
||
|
||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||
|
||
function L(key: keyof typeof ui_locales): string {
|
||
return ui_locales[key][currentLanguageIndex];
|
||
}
|
||
|
||
export interface TreeWasteDraft {
|
||
tree_waste: Record<string, number | null>;
|
||
tree_waste_unit_price_krw_per_ton: number | null;
|
||
waste_separate_order: boolean;
|
||
/** 뿌리 산정법 — `""` 분배비 15/85(기본) · "root_ball" 뿌리분 체적 × 1,300. */
|
||
tree_waste_root_method: string;
|
||
dirty: boolean;
|
||
}
|
||
|
||
/** 페이지의 칸 만들기 도우미 — 같은 모양을 쓰려고 받아 씀. */
|
||
export interface SideFieldHelpers {
|
||
field(label: string, value: string): HTMLElement;
|
||
optionalNumberField(
|
||
label: string,
|
||
value: number | null,
|
||
step: string,
|
||
onInput: (value: number | null) => void,
|
||
): HTMLElement;
|
||
selectField(
|
||
label: string,
|
||
value: string,
|
||
options: { value: string; label: string }[],
|
||
onChange: (value: string) => void,
|
||
): HTMLElement;
|
||
hintRow(text: string): HTMLElement;
|
||
}
|
||
|
||
/** 조사값 칸 — 서버 `TREE_WASTE_INPUTS` 와 같은 키. */
|
||
const INPUTS: [string, keyof typeof ui_locales, string][] = [
|
||
["stems_per_1000m2", "B08_Quantity_TreeWaste_Stems", "1"],
|
||
["dbh_cm", "B08_Quantity_TreeWaste_Dbh", "0.1"],
|
||
["height_m", "B08_Quantity_TreeWaste_Height", "0.1"],
|
||
["unit_weight_kg_m3", "B08_Quantity_TreeWaste_UnitWeight", "10"],
|
||
];
|
||
|
||
/** 수동 단가 칸 — 값이 있으면 빨간 테두리(미확정). */
|
||
function markManual(row: HTMLElement, value: number | null): void {
|
||
const input = row.querySelector("input");
|
||
if (input) input.style.outline = value ? "2px solid var(--color-danger, #d9534f)" : "";
|
||
}
|
||
|
||
export function appendTreeWasteFields(
|
||
panel: HTMLElement,
|
||
draft: TreeWasteDraft,
|
||
h: SideFieldHelpers,
|
||
): void {
|
||
panel.append(h.field(L("B08_Quantity_Side_TreeWaste"), ""));
|
||
for (const [key, label, step] of INPUTS) {
|
||
panel.append(
|
||
h.optionalNumberField(L(label), draft.tree_waste[key] ?? null, step, (value) => {
|
||
draft.tree_waste[key] = value;
|
||
draft.dirty = true;
|
||
}),
|
||
);
|
||
}
|
||
panel.append(h.hintRow(L("B08_Quantity_Side_TreeWaste_Hint")));
|
||
|
||
// 뿌리 — 지침 부록1 이 두 방법을 둠. 뿌리분 직경은 흉고직경과 갈라 따로 받음(지침 표기가 모호).
|
||
panel.append(
|
||
h.selectField(
|
||
L("B08_Quantity_TreeWaste_RootMethod"),
|
||
draft.tree_waste_root_method,
|
||
[
|
||
{ value: "", label: L("B08_Quantity_TreeWaste_RootRatio") },
|
||
{ value: "root_ball", label: L("B08_Quantity_TreeWaste_RootBall") },
|
||
],
|
||
(value) => {
|
||
draft.tree_waste_root_method = value;
|
||
draft.dirty = true;
|
||
},
|
||
),
|
||
);
|
||
panel.append(
|
||
h.optionalNumberField(
|
||
L("B08_Quantity_TreeWaste_RootBallDiameter"),
|
||
draft.tree_waste["root_ball_diameter_m"] ?? null,
|
||
"0.01",
|
||
(value) => {
|
||
draft.tree_waste["root_ball_diameter_m"] = value;
|
||
draft.dirty = true;
|
||
},
|
||
),
|
||
);
|
||
panel.append(h.hintRow(L("B08_Quantity_TreeWaste_Root_Hint")));
|
||
|
||
const price = h.optionalNumberField(
|
||
L("B08_Quantity_TreeWaste_Price"),
|
||
draft.tree_waste_unit_price_krw_per_ton,
|
||
"1000",
|
||
(value) => {
|
||
draft.tree_waste_unit_price_krw_per_ton = value;
|
||
draft.dirty = true;
|
||
markManual(price, value);
|
||
},
|
||
);
|
||
markManual(price, draft.tree_waste_unit_price_krw_per_ton);
|
||
panel.append(price);
|
||
const priceHint = h.hintRow(L("B08_Quantity_TreeWaste_Price_Hint"));
|
||
priceHint.classList.add("b08-quantity__hint--warn");
|
||
panel.append(priceHint);
|
||
|
||
panel.append(
|
||
h.selectField(
|
||
L("B08_Quantity_TreeWaste_Separate"),
|
||
draft.waste_separate_order ? "on" : "",
|
||
[
|
||
{ value: "", label: L("B08_Quantity_TreeWaste_Separate_Off") },
|
||
{ value: "on", label: L("B08_Quantity_TreeWaste_Separate_On") },
|
||
],
|
||
(value) => {
|
||
draft.waste_separate_order = value === "on";
|
||
draft.dirty = true;
|
||
},
|
||
),
|
||
);
|
||
panel.append(h.hintRow(L("B08_Quantity_TreeWaste_Separate_Hint")));
|
||
}
|