// 원본 품셈 보기(barbtnBook → ProductBook.exe · 같은 화면 TfrmBookViewer)와 품셈자료 붙여넣기(popupPasteBook 10d6e28). // 보기: 품셈 고르기 → 장 트리(펼치기·접기) → 본문(표는 칸 나눔), 검색(제목 · 제목+본문 · 키워드, 검색어 빈칸·'|' 지움), // 본문 검색(현재 쪽 강조), 복사하기(고른 글 또는 쪽 전체 → 클립보드, 붙여넣기 켬). 자료는 서버 book-store.cjs 가 원본 BookData 를 읽음. // 붙여넣기(원본은 품셈에서 복사한 뒤에만 켜짐): 단가산출은 첫 줄을 마지막 산출근거 칸 커서 자리에, 둘째 줄부터 그 줄 아래 새 줄로(다듬지 않음). // 내역서·일위대가·기계경비는 마지막으로 누른 칸 하나에 마지막 줄(글자 칸은 Trim, 수치 칸은 수치만). const EstBook = (() => { let lastCell = null; if (typeof document !== "undefined" && document.addEventListener) document.addEventListener("focusin", (event) => { const t = event.target.closest?.("[data-table][data-field][data-row]"); if (t) lastCell = t; }); const api = (p) => EstApp.api(`/api/book${p}`); const st = (state) => (state.book ||= { name: "", open: new Set(), hits: null, pkid: null }); function treeHtml(b, nodes) { const { esc } = EstApp, kids = new Map(); for (const n of nodes) { const k = n.parent ?? "root"; if (!kids.has(k)) kids.set(k, []); kids.get(k).push(n); } for (const list of kids.values()) list.sort((a, c) => (a.order == null) - (c.order == null) || (a.order ?? 0) - (c.order ?? 0)); const walk = (key, depth) => (kids.get(key) || []) .map((n) => { const has = kids.has(n.pkid), open = b.open.has(n.pkid); return `
| ${hl(c)} | `).join("")}
검색 결과 ${b.hits.length}건
${b.hits.map((h) => ``).join("")}` : treeHtml(b, b.tree); EstApp.showDialog( "품셈", `왼쪽에서 항목을 고르세요.
"}