/* ============================================================================= * B09_Estimation_UI_DetailEdit.ts * 호표 본표 **편집** — 일위대가 구성행 수정(`wM_Edit_iLWi`) · 단가산출 Q 식 편집(`wM_Edit_San`) (PLAN 12장 2차) * * - 전부 프로젝트 단위(브레인 판정) — 칸 하나 고칠 때마다 서버에 고친 값만 보내고, 금액은 서버가 다시 셈. * - 줄마다: 수량 칸 · ✕(줄 빼기) · D 의 기계 줄은 Q 식 칸 · 고친 줄 = 「사용자」 + ↺(계산값으로). * - 아래: 줄 더하기 — 단가표 고르개(이름으로 찾기) + 수량. * - ⚠ 화면이 값을 만들지 않음 — 입력한 글을 그대로 보내고, 받을 수 없는 값은 서버가 까닭과 함께 거절. * ========================================================================== */ import { showToast } from "@ui/ui_template_elements"; import { API_BASE_URL } from "@config/config_frontend"; import { L, el, userMark } from "./B09_Estimation_UI_Sheet"; import { saveEdits, type EditChange } from "./B09_Estimation_UI_Store"; /** 서버가 줄마다 붙이는 고친 값 표시. */ export interface RowEditDto { key: string; user: boolean; removed?: boolean; added?: boolean; was_quantity?: string; output?: string; q_formula?: string; } interface SearchRowDto { code: string; name: string; spec: string; unit: string; kind_label: string; } export const ROWS_SECTION = "sheet_rows"; export const Q_SECTION = "price_basis_q"; function save(projectId: string, changes: EditChange[], reload: () => void): void { void saveEdits(projectId, changes) .then(() => { showToast(L("B09_Sheet_Saved"), "success"); reload(); }) .catch((error: Error) => showToast(`${L("B09_Sheet_SaveFailed")} ${error.message}`, "error")); } /** 수량 칸 — Enter·칸 나가기에 보냄(바뀐 때만). */ export function quantityInput( projectId: string, edit: RowEditDto, quantity: string, reload: () => void, ): HTMLElement { const input = el("input", "b09s-edit-input"); input.type = "text"; input.inputMode = "decimal"; input.value = quantity; const send = (): void => { const text = input.value.trim(); if (text === quantity) return; save(projectId, [{ section: ROWS_SECTION, key: edit.key, value: { quantity: text } }], reload); }; input.addEventListener("click", (event) => event.stopPropagation()); input.addEventListener("keydown", (event) => { if (event.key === "Enter") input.blur(); }); input.addEventListener("change", send); return input; } /** 줄 끝 조작 — ✕(빼기) · 고친 줄이면 「사용자」 + ↺. */ export function rowControls( projectId: string, edit: RowEditDto, reload: () => void, editing: boolean, ): HTMLElement { const box = el("span", "b09s-inline"); if (editing && !edit.removed && !edit.added) { const remove = el("button", "b09s-undo", "✕"); remove.type = "button"; remove.title = L("B09_Sheet_RemoveRow"); remove.addEventListener("click", (event) => { event.stopPropagation(); save(projectId, [{ section: ROWS_SECTION, key: edit.key, value: { removed: true } }], reload); }); box.append(remove); } if (edit.user) { box.append( userMark(() => save(projectId, [{ section: ROWS_SECTION, key: edit.key, value: null }], reload), ), ); } return box; } /** D 기계 줄의 Q 식 칸 — 식을 글로 넣으면 서버가 셈 → 소수 2자리 확정 → 줄 수량 = 몫 ÷ Q. */ export function qFormulaInput( projectId: string, edit: RowEditDto, reload: () => void, ): HTMLElement { const box = el("div", "b09s-inline"); const input = el("input", "b09s-edit-input b09s-edit-formula"); input.type = "text"; input.placeholder = `Q = ${edit.output ?? ""}`; input.value = edit.q_formula ?? ""; input.addEventListener("click", (event) => event.stopPropagation()); input.addEventListener("keydown", (event) => { if (event.key === "Enter") input.blur(); }); input.addEventListener("change", () => { const text = input.value.trim(); const value = text ? { q_formula: text } : null; save(projectId, [{ section: Q_SECTION, key: edit.key, value }], reload); }); box.append(el("span", "b09s-head", "Q"), input); if (edit.q_formula) { box.append( userMark(() => save(projectId, [{ section: Q_SECTION, key: edit.key, value: null }], reload)), ); } return box; } /** 줄 더하기 — 단가표에서 이름으로 찾아 고르고 수량을 넣음. */ export function addRowForm( projectId: string, code: string, nextKey: string, reload: () => void, ): HTMLElement { const box = el("div", "b09s-bar"); const search = el("input", "b09s-edit-input b09s-edit-formula"); search.type = "text"; search.placeholder = L("B09_Sheet_SearchTitle"); const pick = el("select"); const amount = el("input", "b09s-edit-input"); amount.type = "text"; amount.inputMode = "decimal"; amount.placeholder = L("B09_Sheet_Col_Quantity"); const add = el("button", "b09s-undo", L("B09_Sheet_AddRow")); add.type = "button"; let timer = 0; search.addEventListener("input", () => { window.clearTimeout(timer); timer = window.setTimeout(() => { const query = search.value.trim(); if (!query) return; void fetch( `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/estimation/edits/search?q=${encodeURIComponent(query)}&parent=${encodeURIComponent(code)}`, { credentials: "include" }, ) .then((response) => response.json() as Promise<{ rows: SearchRowDto[] }>) .then((data) => { pick.replaceChildren( ...data.rows.map((row) => { const option = el( "option", "", `[${row.kind_label}] ${row.name} ${row.spec} (${row.unit})`.trim(), ); option.value = row.code; return option; }), ); }) .catch(() => pick.replaceChildren()); }, 250); }); add.addEventListener("click", () => { if (!pick.value || !amount.value.trim()) return; save( projectId, [ { section: ROWS_SECTION, key: nextKey, value: { ref: pick.value, quantity: amount.value.trim() }, }, ], reload, ); }); box.append(el("span", "b09s-head", L("B09_Sheet_AddRow")), search, pick, amount, add); return box; }