// 줄 쌓기 단가산출 시험 — 왼쪽 줄 쌓기 편집(종류마다 필요한 칸만 · 칸은 data-p 경로로 doc 에 바로 씀) import { LABOR, MACHINE, MATERIAL, SANGEUN, TEMPLATES } from "./samples.js"; import { esc } from "./render.js"; export const KINDS = { mach: "① 기계", ref: "② 다른 단산 참조", labor: "③ 인력", rate: "④ 율", mat: "⑤ 재료", formula: "⑥ 공식", text: "⑦ 글 · 근거", }; const COLSEL = { l: "노무비", m: "재료비", e: "경비" }; const inp = (p, v, cls = "", ph = "") => ``; const sel = (p, v, opts) => ``; const pick = (list, act, label) => ``; export function allTemplates(mine) { return [...TEMPLATES, ...mine]; } /** 「+ 줄」 고르개 — 공식은 틀마다 하나씩 */ export function addLineSelect(i, mine) { const tpls = allTemplates(mine) .map((t) => ``) .join(""); return ``; } function opsEdit(p, ops) { const one = (o, k) => `${sel(`${p}.ops.${k}.op`, o.op, { "×": "×", "÷": "÷", "/": "/" })}${inp( `${p}.ops.${k}.v`, o.v, "v", "값 · 이름", )}${inp(`${p}.ops.${k}.u`, o.u, "u", "단위")}`; return `
${(ops ?? []).map(one).join("")}
`; } const copsNote = (ln) => ln.cops ? `
비목 따로 꼬리(붙여넣기): ${Object.entries(ln.cops) .map( ([c, ops]) => `${COLSEL[c]} ${ops.map((o) => `${o.op} ${esc(o.v)} ${esc(o.u)}`).join(" ")}`, ) .join(" · ")}
` : ""; /** 공식 줄 — 접힘 = 값 칸만 · 풀림 = 「이름 = 식」 글 줄 전부 */ function formulaEdit(p, ln, r, mine) { const tpl = allTemplates(mine).find((t) => t.id === ln.tpl); const changed = tpl && JSON.stringify(shape(tpl.rows)) !== JSON.stringify(shape(ln.rows)); const label = !tpl ? ln.tpl === null && !ln.rows?.some((x) => x.x) ? "새 공식" : "내 공식" : changed ? `내 틀 (${esc(tpl.name)} 에서 변경)` : esc(tpl.name); const rows = (ln.rows ?? []) .map((row, k) => { const rr = r?.rows?.[k] ?? {}; const rp = `${p}.rows.${k}`; const plain = /^-?[\d.]+$/.test(String(row.x ?? "").trim()); const val = `${rr.err ? esc(rr.err) : (rr.val ?? "")}`; const src = row.src ? `표` : ""; const table = ``; if (!ln.open) { if (plain || !row.x) return `
${esc(row.n || "?")} = ${inp(`${rp}.x`, row.x, "num")} ${esc(row.u)} ${table}${src}
`; return `
${esc(row.n)} = ${esc(row.x)} = ${val} ${esc(row.u)}
`; } return `
${inp(`${rp}.n`, row.n, "name", "이름")} = ${inp(`${rp}.x`, row.x, `expr ${rr.err ? "bad" : ""}`, "값 또는 식")}${inp( `${rp}.u`, row.u, "u", "단위", )}${val}${table}${src}
`; }) .join(""); return `
${label}
${rows} ${ln.open ? `` : ""}`; } /** 틀과 견줄 꼴 — 값 줄은 이름만 · 식 줄은 이름과 식 */ export function shape(rows) { return (rows ?? []).map((x) => /^-?[\d.]+$/.test(String(x.x).trim()) ? [x.n] : [x.n, String(x.x).replace(/\s/g, "")], ); } function lineBody(p, ln, r, mine) { const price3 = () => `
노무 ${inp(`${p}.res.l`, ln.res?.l, "num")} 재료 ${inp(`${p}.res.m`, ln.res?.m, "num")} 경비 ${inp( `${p}.res.e`, ln.res?.e, "num", )}
`; switch (ln.k) { case "mach": return `
${pick(MACHINE, `res:mach:${p}`, "견본 기계 ▾")}${inp(`${p}.res.name`, ln.res?.name, "wide", "기계 이름")}
${price3()}${opsEdit(p, ln.ops)}${copsNote(ln)}`; case "ref": return `
${pick(SANGEUN, `res:ref:${p}`, "견본 단산 ▾")} 단산 ${inp(`${p}.res.no`, ln.res?.no, "num s")}${inp( `${p}.res.name`, ln.res?.name, "wide", "단가산출 이름", )}
${price3()}${opsEdit(p, ln.ops)}${copsNote(ln)}`; case "labor": case "mat": return `
${pick(ln.k === "labor" ? LABOR : MATERIAL, `res:${ln.k}:${p}`, ln.k === "labor" ? "견본 노임 ▾" : "견본 자재 ▾")}${inp( `${p}.res.name`, ln.res?.name, "wide", "이름", )} 단가 ${inp(`${p}.res.price`, ln.res?.price, "num")}${ln.k === "mat" ? ` → ${sel(`${p}.col`, ln.col || "m", COLSEL)}` : ""}
${opsEdit(p, ln.ops)}`; case "rate": return `
${inp(`${p}.name`, ln.name, "wide", "이름(예: 공구손료 및 경장비)")}
기준 ${sel(`${p}.scope`, ln.scope || "item", { item: "이 항목 위 줄", all: "위 전부" })} 의 ${sel(`${p}.baseCol`, ln.baseCol || "l", COLSEL)} × ${inp(`${p}.rate`, ln.rate, "num s")} % → ${sel(`${p}.col`, ln.col || "m", COLSEL)}
글 「( ${inp(`${p}.of`, ln.of, "s2", "노무비")}의 % )」 · 소계 이름 ${inp(`${p}.baseName`, ln.baseName, "s2", "비우면 안 찍음")}
`; case "formula": return formulaEdit(p, ln, r, mine); case "text": return inp(`${p}.text`, ln.text, "full", "< 품셈 근거 > · ※ 메모 · 별산"); } return ""; } export function renderEdit(doc, res, mine) { const items = (doc.items ?? []) .map((item, i) => { const lines = (item.lines ?? []) .map((ln, j) => { const p = `items.${i}.lines.${j}`; const r = res.items[i]?.lines[j]; return `
⠿${KINDS[ln.k]}${ r?.err ? `${esc(r.err)}` : "" }
${lineBody(p, ln, r, mine)}
`; }) .join(""); return `
${inp(`items.${i}.title`, item.title, "title", "항목 이름(비우면 번호 없음)")} ${addLineSelect(i, mine)}
${lines || '
줄 없음 — [+ 줄 ▾]
'}
`; }) .join(""); return `
이름 ${inp("name", doc.name, "wide")} 단위 ${inp("unit", doc.unit, "s")}
${items}`; }