- 코드 등록·삭제·변경·병합·데이타병합·일괄변경·검색·동일코드·풀어넣기 끝전 등(ui_code · ui_merge · api_ui_code) - 코드 붙은 % 줄 = 같은 상위 공종 앞 말단 줄 비목 합 × 율(server pct_row_base · calc.js) · 저장 때 다시 셈(stc_save _pct_rows) — 실무 7건 저장값 그대로 - 내역 줄 단가 ≠ 단가목록이면 줄 고정 단가(fixedUnit) - 메뉴 94개 눌러 오류 0 · 시험 502 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
425 lines
19 KiB
JavaScript
425 lines
19 KiB
JavaScript
/**
|
|
* STMATE w 8.0 — 설계내역서 편집 화면(wBoq) 끼워 넣기 (scr_ui_boq.js)
|
|
* 근거: ui_form_catalog.txt wBoq(cBox01 「= 이상」~「====== 이상」 · RGrpTo 내역서/열린 보관함 · cBox02)
|
|
* 편집 계산 = ui_boq.js · 도구 대화상자 = scr_ui_boq_dlg.js · 금액 = view_prj_boq.evaluateFullBoqRows
|
|
* 행 추가·삭제·이동·복사·계표시는 dbfData.boq_rows(저장 원천)와 state.boqItems(계산 원천)를 함께 고침
|
|
*/
|
|
(function (global) {
|
|
"use strict";
|
|
|
|
const P = (global.STMATE_PLUGINS = global.STMATE_PLUGINS || {
|
|
screens: {},
|
|
actions: {},
|
|
menus: [],
|
|
});
|
|
const B = global.STMATE_UI_BOQ;
|
|
const LV = ["= 이상", "== 이상", "=== 이상", "==== 이상", "===== 이상", "====== 이상"];
|
|
const esc = (s) =>
|
|
String(s == null ? "" : s).replace(
|
|
/[&<>"]/g,
|
|
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c],
|
|
);
|
|
|
|
// 편집 모델 — 지운 원본 행 번호는 dbfData.boq_deleted(저장 묶음 boq_deleted 로 나감)
|
|
function model(ctx) {
|
|
const d = ctx.dbfData;
|
|
d.boq_deleted = d.boq_deleted || [];
|
|
return { rows: d.boq_rows, items: ctx.state.boqItems, deleted: d.boq_deleted };
|
|
}
|
|
const costnByCode = (ctx) =>
|
|
Object.fromEntries(
|
|
((ctx.dbfData && ctx.dbfData.costn_rows) || []).map((r) => [String(r.CODE || "").trim(), r]),
|
|
);
|
|
const sel = (ctx) => (ctx.uiState.boqSel || []).map(Number);
|
|
const selIdx = (ctx) => B.indexesOf(ctx.dbfData.boq_rows, sel(ctx));
|
|
|
|
// 편집 뒤 — 결과 알림 · 내역 표 묶음 맞춤 · 다시 그림
|
|
function after(ctx, api, res, keepSel) {
|
|
if (res && res.ok === false) alert(res.error);
|
|
const d = ctx.dbfData;
|
|
d.boq_tables = d.boq_tables || {};
|
|
d.boq_tables[d.boq_table_name] = d.boq_rows;
|
|
d.boq_count = d.boq_rows.length;
|
|
if (!keepSel)
|
|
ctx.uiState.boqSel = sel(ctx).filter((r) => d.boq_rows.some((x) => Number(x._recno) === r));
|
|
api.renderWorkspace();
|
|
return res;
|
|
}
|
|
|
|
const insertAt = (ctx) => {
|
|
const ix = selIdx(ctx);
|
|
return ix.length ? ix[0] : ctx.dbfData.boq_rows.length;
|
|
};
|
|
|
|
// 화면 동작 표 — 메뉴줄·단추·단축키가 함께 씀
|
|
const ACTS = {
|
|
insBlank: (ctx, api) => {
|
|
const m = model(ctx);
|
|
const row = B.makeRow(m, {});
|
|
const res = B.insertRows(m, insertAt(ctx), [row]);
|
|
ctx.uiState.boqSel = [row._recno];
|
|
return after(ctx, api, res, true);
|
|
},
|
|
del: (ctx, api) => {
|
|
if (!sel(ctx).length) return alert("지울 행을 고르세요");
|
|
if (!confirm(`선택한 ${sel(ctx).length}행을 지움(계표시는 짝도 함께) — 계속?`)) return;
|
|
return after(ctx, api, B.deleteRows(model(ctx), sel(ctx)));
|
|
},
|
|
up: (ctx, api) => after(ctx, api, B.moveRows(model(ctx), sel(ctx), -1), true),
|
|
down: (ctx, api) => after(ctx, api, B.moveRows(model(ctx), sel(ctx), 1), true),
|
|
copy: (ctx) => {
|
|
if (!sel(ctx).length) return alert("복사할 행을 고르세요");
|
|
ctx.uiState.boqClip = B.copyRows(model(ctx), sel(ctx));
|
|
ctx.uiState.boqMsg = `복사 ${ctx.uiState.boqClip.rows.length}행(계표시는 짝 구간 전체)`;
|
|
global.STMATE_APP.renderWorkspace();
|
|
},
|
|
cut: (ctx, api) => {
|
|
if (!sel(ctx).length) return alert("잘라낼 행을 고르세요");
|
|
ctx.uiState.boqClip = B.copyRows(model(ctx), sel(ctx));
|
|
const all = ctx.uiState.boqClip.rows.length;
|
|
const res = B.deleteRows(model(ctx), sel(ctx));
|
|
ctx.uiState.boqMsg = `잘라내기 ${all}행`;
|
|
return after(ctx, api, res);
|
|
},
|
|
paste: (ctx, api) =>
|
|
after(ctx, api, B.pasteRows(model(ctx), insertAt(ctx), ctx.uiState.boqClip)),
|
|
lvUp: (ctx, api) => shift(ctx, api, 1),
|
|
lvDn: (ctx, api) => shift(ctx, api, -1),
|
|
note2grp: (ctx, api) => {
|
|
const r = sel(ctx)[0];
|
|
const n = Number(
|
|
prompt("계시작 레벨(1~9) — 1 소계 · 2 계 · 3 합계 · 4 총계 · 5 총합계 · 6 총대계", "2"),
|
|
);
|
|
if (!(n >= 1 && n <= 9)) return;
|
|
return after(ctx, api, B.noteToGroup(model(ctx), r, n, ctx.dbfData.start_ini));
|
|
},
|
|
find: (ctx, api) => {
|
|
const kw = String(ctx.uiState.boqFind || "")
|
|
.trim()
|
|
.toLowerCase();
|
|
if (!kw) return alert("찾을 글자를 넣으세요");
|
|
const rows = ctx.dbfData.boq_rows;
|
|
const from = (selIdx(ctx)[0] ?? -1) + 1;
|
|
const hay = (r) =>
|
|
`${r.ITNUM || ""} ${r.BDCODE1 || ""} ${r.NAME1 || ""} ${r.SIZE1 || ""}`.toLowerCase();
|
|
let k = rows.findIndex((r, i) => i >= from && hay(r).includes(kw));
|
|
if (k < 0) k = rows.findIndex((r) => hay(r).includes(kw));
|
|
if (k < 0) return alert(`「${kw}」 없음`);
|
|
Object.assign(ctx.uiState, {
|
|
boqSel: [Number(rows[k]._recno)],
|
|
boqLevelMin: 0,
|
|
boqScrollTo: rows[k]._recno,
|
|
});
|
|
api.renderWorkspace();
|
|
},
|
|
pocketPut: (ctx, api) => {
|
|
if (!sel(ctx).length) return alert("보관함에 담을 행을 고르세요");
|
|
const u = ctx.uiState,
|
|
n = u.activePocket || 1;
|
|
u.boqPockets = u.boqPockets || { 1: [], 2: [], 3: [] };
|
|
const clip = B.copyRows(model(ctx), sel(ctx));
|
|
clip.title = clip.rows.map((r) => r.NAME1 || r.BDCODE1).filter(Boolean)[0] || "(빈 행)";
|
|
u.boqPockets[n].push(clip);
|
|
api.renderWorkspace();
|
|
},
|
|
// 호표 코드 → 단가목록표(기초단가·견적) · 대가표(일위대가·산근·중기)
|
|
jump: (ctx, api) => {
|
|
const r = ctx.dbfData.boq_rows.find((x) => Number(x._recno) === sel(ctx)[0]);
|
|
const c = r && B.isLeaf(r) ? B.codeOf(r) : "";
|
|
if (!c) return alert("단가코드 행을 고르세요");
|
|
if (/^[MLSW]/.test(c)) {
|
|
ctx.uiState.dan = Object.assign(ctx.uiState.dan || {}, {
|
|
kind: "ALL",
|
|
kw: c,
|
|
mode: "name",
|
|
code: c,
|
|
});
|
|
ctx.uiState.activeScreen = "wM_JabPum";
|
|
} else Object.assign(ctx.uiState, { selectedIlwiCode: c, activeScreen: "wM_Edit_iLWi" });
|
|
api.renderWorkspace();
|
|
},
|
|
qtyList: (ctx, api) => {
|
|
if (P.screens.wQtyList)
|
|
return ((ctx.uiState.activeScreen = "wQtyList"), api.renderWorkspace());
|
|
api.openMappedModal("Act_QtyModal");
|
|
},
|
|
};
|
|
|
|
function shift(ctx, api, d) {
|
|
const r = sel(ctx)[0];
|
|
if (r == null) return alert("계표시(머리·계) 행을 고르세요");
|
|
return after(ctx, api, B.shiftLevel(model(ctx), r, d, ctx.dbfData.start_ini), true);
|
|
}
|
|
|
|
const MENUS = [
|
|
[
|
|
"편집",
|
|
[
|
|
["insBlank", "행 삽입 (Ins)"],
|
|
["kongjong", "공종목록에서 코드 넣기"],
|
|
["del", "행 삭제 (Del)"],
|
|
["jump", "코드의 단가·대가표로 이동"],
|
|
null,
|
|
["copy", "복사 (Ctrl+C)"],
|
|
["cut", "잘라내기 (Ctrl+X)"],
|
|
["paste", "붙여넣기 (Ctrl+V)"],
|
|
null,
|
|
["up", "위로 이동 (Alt+↑)"],
|
|
["down", "아래로 이동 (Alt+↓)"],
|
|
],
|
|
],
|
|
[
|
|
"계표시",
|
|
[
|
|
["wrap", "계표시 삽입(선택 행 묶음)"],
|
|
["lvUp", "계표시 레벨 상향"],
|
|
["lvDn", "계표시 레벨 하향"],
|
|
["note2grp", "주석 → 계시작 변환"],
|
|
],
|
|
],
|
|
[
|
|
"도구",
|
|
[
|
|
["itnum", "공종번호 자동입력 (wITNum)"],
|
|
["boqRate", "할증율 적용 (wBoqRate)"],
|
|
["mRate", "단수 일괄보정 (wM_Rate)"],
|
|
["expnd", "일위대가표 풀기 (wExpnd)"],
|
|
null,
|
|
["mboq", "주공종 집계표 (wMBoq)"],
|
|
["qtyList", "수량산출서 (wQtyList)"],
|
|
null,
|
|
["openSame", "동일코드 추려보기 (wSameCode)"],
|
|
["openMemo", "메모 입력 (wInDlgMemo)"],
|
|
["openSpChar", "특수문자 (mMinMsg_)"],
|
|
["openUser", "사용자문자 (wM_Edit_USER)"],
|
|
],
|
|
],
|
|
["보관함", [["pocketPut", "선택 행 보관함에 담기"]]],
|
|
];
|
|
|
|
function menuBar() {
|
|
return `<div class="sub-menubar">${MENUS.map(
|
|
([cap, items]) =>
|
|
`<div class="menu-item"><span>${cap}</span><div class="menu-dropdown">${items
|
|
.map((x) => (x ? `<button data-boq-act="${x[0]}">${x[1]}</button>` : "<hr />"))
|
|
.join("")}</div></div>`,
|
|
).join("")}</div>`;
|
|
}
|
|
|
|
const money = (fmt, v) => (Number(v) ? fmt(v) : "");
|
|
|
|
function rowHtml(r, ctx, api, selSet) {
|
|
const k = B.rowKind(r),
|
|
bd = B.codeOf(r),
|
|
rec = r._recno;
|
|
const cls = [
|
|
r._rowClass || "",
|
|
selSet.has(Number(rec)) ? "boq-sel" : "",
|
|
r._new ? "boq-new" : "",
|
|
].join(" ");
|
|
const inp = (f, v, extra) =>
|
|
`<input class="cell-input boq-cell ${extra || ""}" data-recno="${rec}" data-f="${f}" value="${esc(v)}" />`;
|
|
const leaf = k.kind === "LEAF";
|
|
const free = leaf || k.kind === "NOTE" || k.kind === "BLANK";
|
|
const hal = [r.RM, r.RL, r.RS].map(Number);
|
|
const halTxt = Number(r.RATE)
|
|
? `할증 ${Number(r.RATE)}%`
|
|
: hal.some(Boolean)
|
|
? `할증 재${hal[0]}·노${hal[1]}·경${hal[2]}`
|
|
: "";
|
|
const { fmt } = api;
|
|
return `<tr class="${cls}" data-recno="${rec}">
|
|
<td class="center rowhead" data-recno="${rec}">${r._idx + 1}</td>
|
|
<td>${inp("ITNUM", r.ITNUM || "", "center")}</td>
|
|
<td class="center">${free ? inp("BDCODE1", bd, "center") : `<strong>${esc(bd)}</strong>`}</td>
|
|
<td>${k.kind === "E" || k.kind === ":" ? esc(r.NAME1 || "") : inp("NAME1", r.NAME1 || "", "name-w")}</td>
|
|
<td>${free ? inp("SIZE1", r.SIZE1 || "") : esc(r.SIZE1 || "")}</td>
|
|
<td class="num">${leaf ? inp("QTY1", B.num(r.QTY1), "num") : ""}</td>
|
|
<td class="center">${free ? inp("UNIT1", r.UNIT1 || "", "center") : ""}</td>
|
|
<td class="num">${leaf ? money(fmt, r.MAMT1) : ""}</td><td class="num">${money(fmt, r.M_AMT1)}</td>
|
|
<td class="num">${leaf ? money(fmt, r.LAMT1) : ""}</td><td class="num">${money(fmt, r.L_AMT1)}</td>
|
|
<td class="num">${leaf ? money(fmt, r.SAMT1) : ""}</td><td class="num">${money(fmt, r.S_AMT1)}</td>
|
|
<td class="num"><strong>${leaf ? money(fmt, r.TAMT1) : ""}</strong></td><td class="num"><strong>${money(fmt, r.T_AMT1)}</strong></td>
|
|
<td class="center">${esc(halTxt)}</td>
|
|
</tr>`;
|
|
}
|
|
|
|
function pocketPane(ctx) {
|
|
const u = ctx.uiState,
|
|
n = u.activePocket || 1;
|
|
const list = ((u.boqPockets || {})[n] || []).filter((p) => p && p.rows);
|
|
const body = list.length
|
|
? `<table class="dbgrid"><thead><tr><th>담은 내용</th><th>행</th><th></th></tr></thead><tbody>${list
|
|
.map(
|
|
(p, i) =>
|
|
`<tr><td>${esc(p.title || "")}</td><td class="num">${p.rows.length}</td><td class="center"><button class="win-btn boq-pocket-ins" data-idx="${i}">넣기</button> <button class="win-btn boq-pocket-del" data-idx="${i}">빼기</button></td></tr>`,
|
|
)
|
|
.join("")}</tbody></table>`
|
|
: `<p class="stm-note">보관함 (${n}) 비어 있음 — 행을 고르고 [보관함 › 선택 행 보관함에 담기]</p>`;
|
|
return `<div class="pane-box" style="width:250px;"><div class="pane-title"><span>내역보관함 (${n})</span><span>${[
|
|
1, 2, 3,
|
|
]
|
|
.map(
|
|
(k) =>
|
|
`<button class="win-btn boq-pocket-tab ${k === n ? "active" : ""}" data-pocket="${k}">(${k})</button>`,
|
|
)
|
|
.join("")}</span></div><div class="grid-scroll" style="padding:6px;">${body}</div></div>`;
|
|
}
|
|
|
|
function render(ctx, api) {
|
|
if (!ctx.dbfData || !ctx.dbfData.boq_rows)
|
|
return `<div class="win-form"><div class="win-form-header"><span>설계내역서 [wBoq]</span></div><p class="stm-note" style="padding:12px;">열린 프로젝트 없음 — [프로젝트 관리]에서 여세요.</p></div>`;
|
|
const u = ctx.uiState;
|
|
const all = global.STMATE_VIEW_PRJ_BOQ.evaluateFullBoqRows(ctx);
|
|
const lv = Number(u.boqLevelMin || 0);
|
|
const kw = String(u.boqSearch || "")
|
|
.trim()
|
|
.toLowerCase();
|
|
let rows = lv ? B.filterLevel(all, lv) : all;
|
|
if (kw)
|
|
rows = rows.filter((r) =>
|
|
`${r.ITNUM || ""} ${r.BDCODE1 || ""} ${r.NAME1 || ""} ${r.SIZE1 || ""}`
|
|
.toLowerCase()
|
|
.includes(kw),
|
|
);
|
|
const selSet = new Set(sel(ctx));
|
|
const ini = ctx.dbfData.start_ini || {};
|
|
const cap = (k, d) => esc(String(ini[k] || d).replace(/\s+/g, " "));
|
|
const bt = ctx.computed.boqComputed.totals;
|
|
const newN = ctx.dbfData.boq_rows.filter((r) => r._new).length,
|
|
delN = (ctx.dbfData.boq_deleted || []).length;
|
|
return `<div class="win-form" id="boq-form">
|
|
<div class="win-form-header"><span>설계내역서 [wBoq · ${esc(ctx.dbfData.boq_table_name || "")}] — 전체 ${all.length}행 · 표시 ${rows.length}행</span><span>직공비 ${api.fmt(bt.directT)}원</span></div>
|
|
${menuBar()}
|
|
<div class="win-subbar">
|
|
<label>수준(cBox01)</label><select id="boq-lv" class="win-select"><option value="0">내역 전체</option>${LV.map((t, i) => `<option value="${i + 1}" ${lv === i + 1 ? "selected" : ""}>${t}</option>`).join("")}</select>
|
|
<input id="boq-search" class="win-input" placeholder="거르기(공종·코드·명칭·규격)" value="${esc(u.boqSearch || "")}" style="width:150px;" />
|
|
<input id="boq-find" class="win-input" placeholder="찾기" value="${esc(u.boqFind || "")}" style="width:110px;" /><button class="win-btn" data-boq-act="find">다음 찾기</button>
|
|
<button class="win-btn" data-boq-act="insBlank">행 삽입</button><button class="win-btn" data-boq-act="kongjong">코드 넣기</button><button class="win-btn" data-boq-act="wrap">계표시 삽입</button><button class="win-btn" data-boq-act="del">삭제</button><button class="win-btn" data-boq-act="jump" title="선택한 단가코드의 단가목록표·대가표로">코드 이동</button>
|
|
<button class="win-btn" data-boq-act="up">▲</button><button class="win-btn" data-boq-act="down">▼</button>
|
|
<button class="win-btn" data-boq-act="copy">복사</button><button class="win-btn" data-boq-act="paste" ${u.boqClip ? "" : "disabled"}>붙여넣기</button>
|
|
<label>넣을 곳(RGrpTo)</label><select id="boq-grpto" class="win-select"><option value="boq">내역서</option><option value="pocket" ${u.boqGrpTo === "pocket" ? "selected" : ""}>열린 보관함</option></select>
|
|
</div>
|
|
<div class="split-horiz">
|
|
<div class="pane-box" style="flex:1;"><div class="grid-scroll" id="boq-grid" tabindex="0"><table class="dbgrid"><thead>
|
|
<tr><th rowspan="2">No</th><th rowspan="2">${cap("CAP_ITNUM", "공종")}</th><th rowspan="2">${cap("CAP_CODE", "코드번호")}</th><th rowspan="2">${cap("CAP_NAME", "명칭")}</th><th rowspan="2">${cap("CAP_SIZE", "규격")}</th><th rowspan="2">${cap("CAP_QTY", "수량")}</th><th rowspan="2">${cap("CAP_SUNIT", "단위")}</th>
|
|
${["BMK_STR1", "BMK_STR2", "BMK_STR3", "BMK_STR4"].map((k, i) => `<th colspan="2">${cap(k, ["재료비", "노무비", "경비", "합계"][i])}</th>`).join("")}<th rowspan="2">${cap("CAP_BIGO", "비고")}</th></tr>
|
|
<tr>${"1234"
|
|
.split("")
|
|
.map(() => `<th>${cap("CAP_AMT", "단가")}</th><th>${cap("CAP_AMOUNT", "금액")}</th>`)
|
|
.join("")}</tr>
|
|
</thead><tbody>${rows.map((r) => rowHtml(r, ctx, api, selSet)).join("")}</tbody></table></div></div>
|
|
${pocketPane(ctx)}
|
|
</div>
|
|
<div class="win-subbar"><span>선택 ${selSet.size}행</span><span>· 새 행 ${newN} · 지운 원본 행 ${delN}</span><span class="stm-note">${esc(u.boqMsg || "")}</span><span class="stm-note" style="margin-left:auto;">행 추가·삭제·차례는 화면·계산에 바로 반영 · [계산 완료] 저장 때 파일에 씀</span></div>
|
|
</div>`;
|
|
}
|
|
|
|
function bind(ctx, api) {
|
|
if (!ctx.dbfData || !ctx.dbfData.boq_rows) return;
|
|
const u = ctx.uiState,
|
|
$ = (id) => document.getElementById(id);
|
|
const run = (act) => {
|
|
u.boqMsg = "";
|
|
if (ACTS[act]) return ACTS[act](ctx, api);
|
|
const D = global.STMATE_UI_BOQ_DLG;
|
|
if (D && D[act])
|
|
return D[act](ctx, api, { model, after, sel, selIdx, costnByCode, insertAt });
|
|
const X = global.STMATE_UI_TEXT; // 추려보기·메모·특수문자·사용자문자 — scr_ui_text.js
|
|
if (X && X[act]) return X[act](ctx, api);
|
|
};
|
|
document
|
|
.querySelectorAll("#boq-form [data-boq-act]")
|
|
.forEach((b) => (b.onclick = () => run(b.getAttribute("data-boq-act"))));
|
|
$("boq-lv").onchange = (e) => ((u.boqLevelMin = Number(e.target.value)), api.renderWorkspace());
|
|
$("boq-search").onchange = (e) => ((u.boqSearch = e.target.value), api.renderWorkspace());
|
|
$("boq-find").onchange = (e) => (u.boqFind = e.target.value);
|
|
$("boq-grpto").onchange = (e) => (u.boqGrpTo = e.target.value);
|
|
document.querySelectorAll("#boq-form td.rowhead").forEach((td) => {
|
|
td.ondblclick = () => run("jump");
|
|
td.onclick = (e) => {
|
|
const rec = Number(td.getAttribute("data-recno"));
|
|
const rows = ctx.dbfData.boq_rows;
|
|
if (e.shiftKey && u.boqAnchor != null) {
|
|
const a = rows.findIndex((r) => Number(r._recno) === u.boqAnchor),
|
|
b = rows.findIndex((r) => Number(r._recno) === rec);
|
|
u.boqSel = rows.slice(Math.min(a, b), Math.max(a, b) + 1).map((r) => Number(r._recno));
|
|
} else if (e.ctrlKey) {
|
|
const s = new Set(sel(ctx));
|
|
s.has(rec) ? s.delete(rec) : s.add(rec);
|
|
u.boqSel = [...s];
|
|
u.boqAnchor = rec;
|
|
} else ((u.boqSel = [rec]), (u.boqAnchor = rec));
|
|
api.renderWorkspace();
|
|
};
|
|
});
|
|
document.querySelectorAll("#boq-form .boq-cell").forEach((inp) => {
|
|
inp.onchange = () => {
|
|
const res = B.setCell(
|
|
model(ctx),
|
|
inp.getAttribute("data-recno"),
|
|
inp.getAttribute("data-f"),
|
|
inp.value,
|
|
costnByCode(ctx),
|
|
);
|
|
after(ctx, api, res, true);
|
|
};
|
|
inp.onfocus = () => {
|
|
const rec = Number(inp.getAttribute("data-recno"));
|
|
if (!sel(ctx).includes(rec)) ((u.boqSel = [rec]), (u.boqAnchor = rec));
|
|
};
|
|
});
|
|
document
|
|
.querySelectorAll("#boq-form .boq-pocket-tab")
|
|
.forEach(
|
|
(b) =>
|
|
(b.onclick = () => (
|
|
(u.activePocket = Number(b.getAttribute("data-pocket"))),
|
|
api.renderWorkspace()
|
|
)),
|
|
);
|
|
const pk = () => (u.boqPockets || {})[u.activePocket || 1] || [];
|
|
document
|
|
.querySelectorAll("#boq-form .boq-pocket-ins")
|
|
.forEach(
|
|
(b) =>
|
|
(b.onclick = () =>
|
|
after(
|
|
ctx,
|
|
api,
|
|
B.pasteRows(model(ctx), insertAt(ctx), pk()[Number(b.getAttribute("data-idx"))]),
|
|
)),
|
|
);
|
|
document
|
|
.querySelectorAll("#boq-form .boq-pocket-del")
|
|
.forEach(
|
|
(b) =>
|
|
(b.onclick = () => (
|
|
pk().splice(Number(b.getAttribute("data-idx")), 1),
|
|
api.renderWorkspace()
|
|
)),
|
|
);
|
|
const grid = $("boq-grid");
|
|
if (u.boqGridScroll) grid.scrollTop = u.boqGridScroll;
|
|
grid.onscroll = () => (u.boqGridScroll = grid.scrollTop);
|
|
if (u.boqScrollTo != null) {
|
|
const tr = grid.querySelector(`tr[data-recno="${u.boqScrollTo}"]`);
|
|
if (tr) tr.scrollIntoView({ block: "center" });
|
|
u.boqScrollTo = null;
|
|
}
|
|
grid.onkeydown = (e) => {
|
|
const typing = /^(INPUT|SELECT|TEXTAREA)$/.test(document.activeElement.tagName);
|
|
const key = (e.ctrlKey ? "C+" : "") + (e.altKey ? "A+" : "") + e.key;
|
|
const map = { Insert: "insBlank", "A+ArrowUp": "up", "A+ArrowDown": "down" };
|
|
if (!typing)
|
|
Object.assign(map, { Delete: "del", "C+c": "copy", "C+x": "cut", "C+v": "paste" });
|
|
if (!map[key]) return;
|
|
e.preventDefault();
|
|
run(map[key]);
|
|
};
|
|
}
|
|
|
|
P.screens.wBoq = { render, bind };
|
|
global.STMATE_UI_BOQ_SCREEN = { ACTS, model, after };
|
|
})(window);
|