/**
* STMATE w 8.0 — 글자 입력 도우미·내역 보조 창 끼워 넣기 (scr_ui_text.js)
* 특수문자(mMinMsg_ · 14종 · 문자 입력후 종료 · 다중문자 동시입력) · 사용자문자 편집(wM_Edit_USER · 검색명칭/입력내용/적용)
* · 메모 입력(wInDlgMemo · Ok/Cancel/Load/Clear — 내역 행 DES1·DES2) · 동일코드 추려보기(wSameCode «추려보기» · N번 사용중 · 더블클릭 → 내역 위치)
* · 실적단가 → 일식단가 변환등록(wM_Conv_A2W · 항목선택 → 비목율 → 변환코드 → 변환/생성)
* 글자 넣는 자리 = 마지막으로 손댄 입력칸·글상자(커서 자리) · 사용자문자 저장 = 브라우저(localStorage, 원본 USR_SYNC.DBF 자리)
*/
(function (global) {
"use strict";
const P = (global.STMATE_PLUGINS = global.STMATE_PLUGINS || {
screens: {},
actions: {},
menus: [],
});
const UA = global.STMATE_UI_CODE_AMT;
const UC = global.STMATE_UI_CODE;
const esc = (s) =>
String(s == null ? "" : s).replace(
/[&<>"]/g,
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c],
);
const $ = (id) => document.getElementById(id);
const num = (v) => Number(String(v == null ? "" : v).replace(/,/g, "")) || 0;
// 마지막 입력 자리 — 창(모달) 안 입력칸은 빼고 작업 화면 칸만 기억
let target = null;
document.addEventListener("focusin", (e) => {
const el = e.target;
if (
/^(INPUT|TEXTAREA)$/.test(el.tagName) &&
!el.closest("#stmate-modal-window") &&
el.type !== "checkbox"
)
target = el;
});
function insertText(s) {
const el = target;
if (!el || !document.body.contains(el))
return (navigator.clipboard && navigator.clipboard.writeText(s), false);
const a = el.selectionStart ?? el.value.length,
b = el.selectionEnd ?? el.value.length;
el.value = el.value.slice(0, a) + s + el.value.slice(b);
el.selectionStart = el.selectionEnd = a + s.length;
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
return true;
}
const where = () =>
target && document.body.contains(target)
? `넣을 자리: ${target.id || target.className || target.tagName}`
: "넣을 자리 없음 — 클립보드로 복사";
// ── 특수문자 ──
const dec = typeof TextDecoder !== "undefined" ? new TextDecoder("euc-kr") : null;
function openSpChar(ctx, api) {
const s = (ctx.uiState.spc = ctx.uiState.spc || { i: 0, close: true, multi: false, buf: "" });
const chars = dec ? UA.specialChars(s.i, (b) => dec.decode(b)) : [];
api.openModal(
"특수문자 [mMinMsg_]",
`
`,
);
$("sc-kind").onchange = (e) => ((s.i = Number(e.target.value)), openSpChar(ctx, api));
$("sc-close").onchange = (e) => (s.close = e.target.checked);
$("sc-multi").onchange = (e) => (s.multi = e.target.checked);
const put = (t) => {
insertText(t);
s.buf = "";
if (s.close) api.closeModal();
};
document
.querySelectorAll(".sc-ch")
.forEach(
(b) =>
(b.onclick = () =>
s.multi
? (($("sc-buf").value += b.textContent), (s.buf = $("sc-buf").value))
: put(b.textContent)),
);
$("sc-put").onclick = () => put($("sc-buf").value);
}
// ── 사용자문자 편집 ──
const UKEY = "stmate_user_text";
const loadU = () => {
try {
return JSON.parse(global.localStorage.getItem(UKEY) || "[]");
} catch (_e) {
return [];
}
};
const saveU = (list) => {
try {
global.localStorage.setItem(UKEY, JSON.stringify(list));
} catch (_e) {
// 저장 공간 없음 — 이번 실행 동안만
}
};
function openUser(ctx, api) {
const list = loadU();
const u = (ctx.uiState.usr = ctx.uiState.usr || { kw: "", sel: 0 });
const shown = UA.userTextFind(
list.map((x, i) => Object.assign({ i }, x)),
u.kw,
).map((x) => x.i);
const all = u.kw ? shown : list.map((_x, i) => i);
api.openModal(
"사용자문자 편집 [wM_Edit_USER]",
``,
);
const re = () => openUser(ctx, api);
const save = () => (saveU(list), re());
$("us-kw").onchange = (e) => ((u.kw = e.target.value), re());
document
.querySelectorAll(".us-r")
.forEach((r) => (r.onchange = () => (u.sel = Number(r.getAttribute("data-i")))));
document
.querySelectorAll(".us-k")
.forEach(
(r) =>
(r.onchange = () => (
(list[Number(r.getAttribute("data-i"))].key = r.value),
saveU(list)
)),
);
document
.querySelectorAll(".us-t")
.forEach(
(r) =>
(r.onchange = () => (
(list[Number(r.getAttribute("data-i"))].text = r.value),
saveU(list)
)),
);
document
.querySelectorAll(".us-on")
.forEach(
(r) =>
(r.onchange = () => (
(list[Number(r.getAttribute("data-i"))].on = r.checked),
saveU(list)
)),
);
document
.querySelectorAll(".us-put")
.forEach(
(b) =>
(b.onclick = () => (
insertText(list[Number(b.getAttribute("data-i"))].text || ""),
api.closeModal()
)),
);
$("us-add").onclick = () => (
list.splice(u.sel + 1, 0, { key: "", text: "", on: true }),
(u.sel += 1),
save()
);
$("us-del").onclick = () => list[u.sel] && (list.splice(u.sel, 1), save());
const mv = (d) => {
const j = u.sel + d;
if (!list[u.sel] || j < 0 || j >= list.length) return;
[list[u.sel], list[j]] = [list[j], list[u.sel]];
u.sel = j;
save();
};
$("us-up").onclick = () => mv(-1);
$("us-dn").onclick = () => mv(1);
$("us-init").onclick = () => confirm("사용자문자 목록을 비움") && (list.splice(0), save());
$("us-sp").onclick = () => openSpChar(ctx, api);
}
// ── 내역 메모(DES1·DES2) ──
const SC = () => global.STMATE_UI_BOQ_SCREEN;
const selRow = (ctx) =>
(ctx.dbfData.boq_rows || []).find((r) => Number(r._recno) === (ctx.uiState.boqSel || [])[0]);
function openMemo(ctx, api) {
const r = selRow(ctx);
if (!r) return alert("메모를 넣을 내역 행을 고르세요");
api.openModal(
`메모 입력 [wInDlgMemo] — ${esc(r.ITNUM || "")} ${esc(r.NAME1 || r.BDCODE1 || "")}`,
``,
);
$("mm-no").onclick = api.closeModal;
$("mm-clr").onclick = () => ($("mm-t").value = "");
$("mm-load").onclick = () => $("mm-file").click();
$("mm-file").onchange = (e) => {
const f = e.target.files[0];
if (f) f.text().then((t) => ($("mm-t").value = t));
};
$("mm-ok").onclick = () => {
const [a, b] = $("mm-t").value.split(/\r?\n/);
Object.assign(r, { DES1: (a || "").trim(), DES2: (b || "").trim() });
api.closeModal();
SC().after(ctx, api, { ok: true }, true);
};
}
// ── 동일코드 추려보기 ──
function openSame(ctx, api) {
const r = selRow(ctx);
const c = r ? String(r.BDCODE1 || "").trim() : "";
if (!c || /^(T\d|E\d|C\d|:)$/.test(c)) return alert("단가코드 행을 고르세요");
const ev = global.STMATE_VIEW_PRJ_BOQ.evaluateFullBoqRows(ctx).filter(
(x) => String(x.BDCODE1 || "").trim() === c,
);
api.openModal(
`추려보기 [wSameCode] — ${c}`,
``,
);
const go = (tr) => {
const rec = Number(tr.getAttribute("data-r"));
Object.assign(ctx.uiState, { boqSel: [rec], boqLevelMin: 0, boqScrollTo: rec });
api.closeModal();
api.renderWorkspace();
};
document
.querySelectorAll(".sm-go")
.forEach(
(tr) => (
(tr.ondblclick = () => go(tr)),
(tr.onkeydown = (e) => e.key === "Enter" && go(tr))
),
);
}
// ── 실적단가 → 일식단가 ──
function openA2W(ctx, api) {
if (!ctx.dbfData) return alert("열린 프로젝트가 없음");
const src = ctx.dbfData.costa_rows || [];
const A = global.STMATE_A2W;
const w = (ctx.uiState.a2w = ctx.uiState.a2w || {
sel: {},
L: 100,
M: 0,
own: false,
start: UC.nextCode(ctx.dbfData.costn_rows, "W"),
});
if (!src.length)
return api.openModal(
"실적단가를 일식단가로 변환등록 [wM_Conv_A2W]",
'이 프로젝트에 실적단가(COSTA.DBF) 자료가 없음
',
);
const ed = A ? A.normalizeEditor(w.L, w.M, "L") : { L: w.L, M: w.M, E: 100 - w.L - w.M };
api.openModal(
"실적단가를 일식단가로 변환등록 [wM_Conv_A2W]",
``,
);
document
.querySelectorAll(".aw-sel")
.forEach((i) => (i.onchange = () => (w.sel[i.getAttribute("data-i")] = i.checked)));
$("aw-ok").onclick = () => {
Object.assign(w, {
L: num($("aw-l").value),
M: num($("aw-m").value),
own: $("aw-own").checked,
start: $("aw-start").value.trim().toUpperCase(),
});
const rows = src.map((r, i) => ({ sel: Boolean(w.sel[i]), rateL: num(r.RATE_L) }));
const rates = A
? A.bulkApply(rows, { L: w.L, M: w.M, sender: "L" }, { individual: w.own })
: rows;
const pick = src.map((r, i) => Object.assign({}, r, rates[i])).filter((_r, i) => w.sel[i]);
if (!pick.length) return alert("변환할 항목을 고르세요");
const taken = new Set(ctx.dbfData.costn_rows.map(UC.codeOf));
const made = UA.a2w(pick, { start: w.start, taken });
const env = { d: ctx.dbfData, state: ctx.state, memo: ctx.uiState.editedSmtMap };
made.forEach((m) =>
UC.addCode(env, {
kind: "W",
code: m.CODE,
fields: {
NAME: m.NAME,
SIZE: m.SIZE,
UNIT: m.UNIT,
MAMT1: String(m.M),
LAMT1: String(m.L),
SAMT1: String(m.S),
TAMT1: String(m.T),
},
}),
);
ctx.uiState.danMsg = `일식단가(W) ${made.length}건 생성 ${made[0].CODE}~`;
w.sel = {};
api.closeModal();
api.cascadeAndRender();
};
}
Object.assign(P.actions, {
Act_UI_SpChar: (ctx, api) => openSpChar(ctx, api),
Act_UI_UserText: (ctx, api) => openUser(ctx, api),
Act_UI_A2W: (ctx, api) => openA2W(ctx, api),
});
P.menus.push(
{ menu: "m4", act: "Act_UI_SpChar", label: "특수문자" },
{ menu: "m4", act: "Act_UI_UserText", label: "사용자문자 편집" },
{ menu: "m2", act: "Act_UI_A2W", label: "실적단가(A) ⇒ 일식단가(W) 변환" },
);
global.STMATE_UI_TEXT = { insertText, openSpChar, openUser, openMemo, openSame, openA2W };
})(window);