/* ============================================================================= * M02_MyMaterial_View.ts * M02 개인관리 › 자재 위 「내 재료」 등록 칸(PLAN 840-4 · 요구 854 · 862) — 개인 층 * 내 재료 = 재료 DB(M01)에 없는 자재를 등록하는 곳 · M01 재료 값 바꿈은 단가산출 안 재료 줄에서 * 표 = 이름 · 규격 · 단위 · 값 · 쓰는 곳 · 키 · 줄 누름 = 아래 칸에서 고치기 · [새 자재] = 빈 칸 * [저장] = 서버(M01 과 이름 · 규격 · 단위 같으면 서버가 막음 — 까닭 그대로 알림) · [지우기] = 쓰는 곳 있으면 서버가 막음 * 값을 고치면 그 재료를 쓰는 개인 층 단가산출이 따라옴(836-2 자동 따라옴 · 프로젝트 층 = 사본 값) * 남의 층(owner 있음) = 읽기만 * ========================================================================== */ import { createButton } from "@ui/ui_template_button"; import { createDataTable } from "@ui/ui_template_data_table"; import { createInputField, el, showConfirmDialog, showToast } from "@ui/ui_template_elements"; import { createText } from "@ui/ui_template_text"; import { createUnitPick, NO_UNIT } from "./DataFlow/M02_DF_UnitPick"; import { addMyMaterial, dropMyMaterial, editMyMaterial, listMyMaterials, type MyMaterial, } from "./M02_MyMaterial_Api"; const TEXT = { title: "내 재료", note: "재료 DB(M01)에 없는 자재를 등록하는 곳 — 단가산출 재료 고르기 「내 재료」 로 씀 · M01 재료 값 바꿈은 단가산출 안 재료 줄에서", none: "내 재료 없음", add: "새 자재", name: "품명", spec: "규격", unit: "단위", value: "값(원)", uses: "쓰는 곳", key: "키", save: "저장", drop: "지우기", cancel: "취소", need: "품명 · 단위는 꼭 적음", saved: "저장함 — 쓰는 단가산출이 새 값으로 셈", dropped: "지움", dropAsk: "「{name}」 을 지움", readOnly: "남의 층 — 읽기만", } as const; export interface MyMaterialBlockOptions { owner: string | null; } export function buildMyMaterialBlock(opt: MyMaterialBlockOptions): HTMLElement { const canWrite = opt.owner === null; let rows: MyMaterial[] = []; let editing: MyMaterial | null = null; let unit = ""; const table = createDataTable({ caption: TEXT.title, columns: [ { key: "이름", label: TEXT.name, width: 200 }, { key: "규격", label: TEXT.spec, width: 140 }, { key: "단위", label: TEXT.unit, width: 60 }, { key: "값", label: TEXT.value, kind: "number", decimals: 2, trim: true, width: 100 }, { key: "쓰는곳", label: TEXT.uses, kind: "number", decimals: 0, width: 70 }, { key: "키", label: TEXT.key, width: 90 }, ], rows: [], empty: TEXT.none, onRowClick: (i) => canWrite && open(rows[i]), }); const name = createInputField({ label: TEXT.name, required: true }); const spec = createInputField({ label: TEXT.spec }); const price = createInputField({ label: TEXT.value, type: "number", min: 0 }); const unitHost = el("span"); const drawUnit = (): void => unitHost.replaceChildren( createUnitPick({ value: unit || NO_UNIT, ariaLabel: TEXT.unit, onPick: (u) => { unit = u === NO_UNIT ? "" : u; drawUnit(); }, }), ); const head = createText(""); const saveBtn = createButton({ label: TEXT.save, variant: "filled", onClick: () => void save() }); const dropBtn = createButton({ label: TEXT.drop, variant: "danger-ghost", onClick: () => void drop(), }); const form = el("div", { className: "m02-pick__new", attrs: { "data-my-material-form": "" }, children: [ head, name.root, spec.root, el("div", { className: "ui-field", children: [el("label", { className: "ui-field__label", text: TEXT.unit }), unitHost], }), price.root, el("div", { className: "ui-btn-group", children: [ createButton({ label: TEXT.cancel, variant: "ghost", onClick: () => close() }), dropBtn, saveBtn, ], }), ], }); form.hidden = true; function open(row: MyMaterial | null): void { editing = row; name.input.value = row?.이름 ?? ""; spec.input.value = row?.규격 ?? ""; price.input.value = row?.값 === null || row?.값 === undefined ? "" : String(row.값); unit = row?.단위 ?? ""; drawUnit(); head.textContent = row ? `${row.키} ${row.이름}` : TEXT.add; dropBtn.hidden = !row; form.hidden = false; name.input.focus(); } function close(): void { editing = null; form.hidden = true; } async function load(): Promise { rows = await listMyMaterials(opt.owner).catch((e) => { showToast(e instanceof Error ? e.message : String(e), "error"); return []; }); table.setRows(rows.map((r) => ({ ...r, 쓰는곳: r.쓰는곳?.length ?? 0 }))); } async function save(): Promise { const 이름 = name.input.value.trim(); if (!이름 || !unit) return showToast(TEXT.need, "error"); const raw = price.input.value.trim(); const body = { 이름, 규격: spec.input.value.trim(), 단위: unit, 값: raw === "" ? null : Number(raw), }; saveBtn.disabled = true; try { if (editing) await editMyMaterial(opt.owner, editing.키, body); else await addMyMaterial(opt.owner, body); showToast(TEXT.saved, "success"); close(); await load(); } catch (e) { showToast(e instanceof Error ? e.message : String(e), "error"); } finally { saveBtn.disabled = false; } } async function drop(): Promise { if (!editing) return; if (!(await showConfirmDialog(TEXT.dropAsk.replace("{name}", editing.이름), TEXT.drop))) return; try { await dropMyMaterial(opt.owner, editing.키); showToast(TEXT.dropped, "success"); close(); await load(); } catch (e) { showToast(e instanceof Error ? e.message : String(e), "error"); } } const addBtn = createButton({ label: TEXT.add, variant: "filled", onClick: () => open(null) }); addBtn.hidden = !canWrite; void load(); return el("div", { attrs: { "data-my-materials": "" }, children: [ el("div", { className: "ui-toolbar", children: [addBtn] }), createText(canWrite ? TEXT.note : TEXT.readOnly, "muted"), table.root, form, ], }); }