- 환경친화적인 도로건설 지침 부록1 확인: 뿌리분 체적 V = 0.3927D³ × 1,300㎏/㎥ — 고르개로 넣음(기본은 분배비 15/85) - 뿌리분 직경 D 는 지침 표기 「뿌리분의 직경(DBH, m)」이 모호해 흉고직경과 따로 받음 - 지상부 식을 지침 원문대로 π 3.14 로 셈 — 실정보고 예 130.40㎥ 와 바로 맞음 - 분리발주 칸 밑·사유에 「임목폐기물은 분리발주 대상 아님(환경부 회신)」 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
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")));
|
||
}
|