- 원가계산서: 폐기물처리비를 경비 줄로(예정가격작성기준 제19조③18호) · 법정경비 뒤라 그 밑수엔 안 섞임 - 분리발주 칸(기본 아님) — 켜면 총원가 밖 · 총공사비에만 더함 - 준비공 「임목폐기물 처리」 톤: WA = 0.5·π·(B/2)²·h·1.3·W1·N · WR = WA × 15/85 (한국건설기술연구원 2012) - 조사값 넷(1,000㎡당 본수·흉고직경·수고·단위체적중량) 칸 · 기본값 없음 · 5톤·100톤 경계 알림 - 수동 처리단가 빨간 테두리 + 「미확정 N건」 · 내역엔 안 서고 제외 사유 「경비」 - 시험: 승률 구조(경비·일반관리비·이윤 밑수 증가 · 법정경비 불변 · 분리발주) · 실정보고 부피 대조 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
102 lines
3.5 KiB
TypeScript
102 lines
3.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;
|
|
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")));
|
|
|
|
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")));
|
|
}
|