213 lines
15 KiB
JavaScript
213 lines
15 KiB
JavaScript
/**
|
|
* STMATE w 8.0 — 프로젝트 관리(Act_Maneger) · 공사목록표(wPrjList · TWPRJLIST) · 종료 화면 끼워 넣기 (scr_ui_prj.js)
|
|
* 근거: REA 분석/09_보조분석/artifacts/resource-TWPRJLIST.dfm.json
|
|
* PnTop 단추(등록정보 수정 · 제잡비 계산 · 해당분 수정 · 복사등록 · 정렬 · 해당분만 압축하기 · 미리보기)
|
|
* MainMenu1(1. 수정 · 2. 내역서 등록 · 3. 해당분압축/조달청포맷 생성 · 4. 보기설정)
|
|
* RGrdPrjList 열(공사번호 · 공사명 · 코드 · 공사종류 · 직공비 · 총공사비 · 복사원본) · RDbGrid_Boq(편집 불가 미리보기)
|
|
* 공사목록 = PRJLST.DBF 행(prjlst_all) — 행마다 내역서 BOQ{PCODE}.DBF 하나
|
|
* wPrjList 는 다른 끼워 넣기가 먼저 등록했으면 그쪽을 씀(파일 담당 화면 우선)
|
|
*/
|
|
(function (global) {
|
|
"use strict";
|
|
|
|
const P = (global.STMATE_PLUGINS = global.STMATE_PLUGINS || { screens: {}, actions: {}, menus: [] });
|
|
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;
|
|
const go = (ctx, api, scr, extra) => (Object.assign(ctx.uiState, { activeScreen: scr }, extra || {}), api.renderWorkspace());
|
|
const activeCode = (ctx) => String((ctx.dbfData && ctx.dbfData.boq_table_name) || "").replace(/^BOQ|\.DBF$/g, "");
|
|
|
|
// ── 프로젝트 관리 ──
|
|
function renderMgr(ctx) {
|
|
const src = (ctx.sourcesData && ctx.sourcesData.projects) || [];
|
|
const kinds = { folder: "PC 폴더", stc: "STC 압축", work_folder: "작업본 폴더" };
|
|
return `<div class="win-form" id="mgr-form">
|
|
<div class="win-form-header"><span>프로젝트 관리 [ 열기, 등록, 백업, 삭제 ]</span><span>${src.length}개</span></div>
|
|
<div class="win-subbar"><button class="win-btn primary" id="mgr-open" ${ctx.uiState.mgrSel ? "" : "disabled"}>열기</button>
|
|
<button class="win-btn" id="mgr-back" ${ctx.dbfData ? "" : "disabled"}>현재 프로젝트 백업</button>
|
|
<button class="win-btn" disabled title="새 프로젝트 폴더 만들기는 서버(파일) 기능 대기">등록</button>
|
|
<button class="win-btn" disabled title="프로젝트 지우기는 서버(파일) 기능 대기">삭제</button>
|
|
<span class="stm-note">열린 프로젝트: ${esc(ctx.currentSourcePath || "(없음)")}</span></div>
|
|
<div class="split-vert"><div class="pane-box" style="flex:1;"><div class="grid-scroll"><table class="dbgrid"><thead><tr><th>No</th><th>구분</th><th>프로젝트</th><th>경로</th></tr></thead><tbody>
|
|
${src
|
|
.map(
|
|
(p, i) =>
|
|
`<tr class="mgr-row ${p.path === ctx.currentSourcePath ? "selected" : ""} ${p.path === ctx.uiState.mgrSel ? "boq-sel" : ""}" data-path="${esc(p.path)}"><td class="center">${i + 1}</td><td class="center">${kinds[p.kind] || esc(p.kind)}</td><td><strong>${esc(p.name)}</strong></td><td><small>${esc(p.path)}</small></td></tr>`,
|
|
)
|
|
.join("")}</tbody></table></div></div></div></div>`;
|
|
}
|
|
|
|
function bindMgr(ctx, api) {
|
|
document.querySelectorAll("#mgr-form .mgr-row").forEach((tr) => {
|
|
tr.onclick = () => ((ctx.uiState.mgrSel = tr.getAttribute("data-path")), api.renderWorkspace());
|
|
tr.ondblclick = () => api.loadProjectFromServer(tr.getAttribute("data-path"), "wPrjList");
|
|
});
|
|
$("mgr-open").onclick = () => api.loadProjectFromServer(ctx.uiState.mgrSel, "wPrjList");
|
|
$("mgr-back").onclick = async () => {
|
|
const r = await api.backup("now");
|
|
if (r) alert(`백업 완료\n• 폴더: ${r.saved_dir}\n• 압축: ${r.saved_stc}`);
|
|
};
|
|
}
|
|
|
|
// ── 공사목록표 ──
|
|
const DISABLED = "아직 만들지 않은 기능";
|
|
const PRJ_MENU = [
|
|
["1. 수 정", [["ActEditBoq", "내역서 수정(&S)"], ["ActSagp", "관(사)급 내역 생성/수정"], ["Act_ED_PNUM", "등록정보 수정 (공사번호/공사명/공사종류)"], ["Act_DN_Chasu", "설계변경내역서 변경차수 내리기"], null, ["Act_Ed_Jab", "제잡비 계산"], ["Act_CalBoq", "선택내역서 재계산"], ["Act_Prn", "선택된 항목의 기본내역서 인쇄"], null, ["ActHaeCost", "해당분 단가 수정작업"], ["Act_Calc_Hae", "해당분 복합단가 전체 재계산"], ["Act_Find_Jod", "조달청제출용 일위대가 내용검증(손료부분,산근포함)"], ["Act_Find_XML", "조달청제출용 대가/산근 내용검증(XML, 손료부분 or 대가수량소수점)"], null, ["Act_Del_Prj", "내역서 삭제"], ["Act_Mer_Prj", "내역서 병합"], null, ["Act_Sagp_Del", "관/사급 내역 삭제"]]],
|
|
["2. 내역서 등록", [["ActNewBoq", "신규등록(&A)"], ["ActAddCopy", "복사등록(&C)"], null, ["ActAddTo5", "할증내역으로 등록"], ["ActMkCont00", "계약내역 등록"], ["Act_MkCont41", "실행예산 복사등록"], ["ActMkCont10", "기성내역 등록"], null, ["ActMkCont21", "설계변경 등록 (당초/변경)"], ["ActMkCont20", "차수별 설계변경 등록"], ["ActMkCont31", "굴착변경 등록"], null, ["Act_Mk_Boq10", "공량내역(기계,전기..) 등록"]]],
|
|
["3. 해당분압축/조달청포맷 생성", [["ActHae_Arc", "선택된 해당분만 압축하기[내역, 대가, 기초단가 ... ]"], null, ["Act_St8_XML", "공사원가호환규정 포맷(XML, 조달청) 변환(해당분)"], ["Act_St8_Jodal1", "JDL 포맷(TEXT) 변환(해당분)"]]],
|
|
["4. 보기설정", [["Act_Find", "문자열 찾기"], ["Act_View_BOQ", "내역서 미리보기/숨기기"], null, ["ActIndex", "공사목록표 내림차순 정렬"], null, ["Act_Indx_PCODE", "공사목록표 코드순 정렬"], ["Act_Indx_PNAME", "공사목록표 공사명순 정렬"], ["Act_Indx_PNUM", "공사목록표 공사번호순 정렬"], null, ["Act_List_UP", "내역목록 위로이동"], ["Act_List_Down", "내역목록 아래로 이동"]]],
|
|
];
|
|
|
|
const prjRows = (ctx) => (ctx.dbfData && ctx.dbfData.prjlst_all) || [];
|
|
const selRow = (ctx) => prjRows(ctx).find((r) => String(r.PCODE).trim() === ctx.uiState.prjSel) || prjRows(ctx)[0];
|
|
const stage = (sub) => (ctx, api) => go(ctx, api, "wStage", { stageSubTab: sub });
|
|
const sortBy = (key, desc) => (ctx, api) => {
|
|
const rows = prjRows(ctx);
|
|
rows.sort((a, b) => String(a[key] || "").localeCompare(String(b[key] || ""), "ko", { numeric: true }) * (desc ? -1 : 1));
|
|
api.renderWorkspace();
|
|
};
|
|
const moveSel = (dir) => (ctx, api) => {
|
|
const rows = prjRows(ctx),
|
|
i = rows.indexOf(selRow(ctx)),
|
|
j = i + dir;
|
|
if (i < 0 || j < 0 || j >= rows.length) return;
|
|
[rows[i], rows[j]] = [rows[j], rows[i]];
|
|
api.renderWorkspace();
|
|
};
|
|
|
|
function needActive(ctx) {
|
|
const r = selRow(ctx);
|
|
if (r && String(r.PCODE).trim() !== activeCode(ctx)) {
|
|
alert(`BOQ${String(r.PCODE).trim()}.DBF 내역서 열기는 서버 불러오기 대기 — 지금 열린 내역서는 BOQ${activeCode(ctx)}`);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function editMeta(ctx, api) {
|
|
const r = selRow(ctx);
|
|
if (!r) return;
|
|
api.openModal(
|
|
"등록정보 수정 (공사번호/공사명/공사종류)",
|
|
`<div class="stm-form"><div class="stm-row"><label>공사번호(PNUM)</label><input id="pm-pnum" class="win-input" value="${esc(r.PNUM)}" style="width:300px;" /></div>
|
|
<div class="stm-row"><label>공사명(PNAME)</label><input id="pm-pname" class="win-input" value="${esc(r.PNAME)}" style="width:420px;" /></div>
|
|
<div class="stm-row"><label>공사종류(PCLASS 코드)</label><input id="pm-pclass" class="win-input" type="number" value="${esc(r.PCLASS)}" style="width:80px;" /><span class="stm-note">코드 뜻은 원본 분석 미확정 — 숫자 그대로</span></div>
|
|
<div class="stm-row"><button class="win-btn primary" id="pm-ok">확인</button></div></div>`,
|
|
);
|
|
$("pm-ok").onclick = () => {
|
|
Object.assign(r, { PNUM: $("pm-pnum").value, PNAME: $("pm-pname").value, PCLASS: String(num($("pm-pclass").value)) });
|
|
if (String(r.PCODE).trim() === activeCode(ctx)) {
|
|
Object.assign(ctx.state.projectMeta, { pname: r.PNAME, pnum: r.PNUM, pclass: num(r.PCLASS) });
|
|
if (ctx.dbfData.prjlst && ctx.dbfData.prjlst !== r) Object.assign(ctx.dbfData.prjlst, { PNUM: r.PNUM, PNAME: r.PNAME, PCLASS: r.PCLASS });
|
|
}
|
|
api.closeModal();
|
|
api.renderWorkspace();
|
|
};
|
|
}
|
|
|
|
const H = {
|
|
ActEditBoq: (ctx, api) => needActive(ctx) && go(ctx, api, "wBoq"),
|
|
ActHaeCost: (ctx, api) => go(ctx, api, "wM_JabPum"),
|
|
Act_ED_PNUM: editMeta,
|
|
Act_Ed_Jab: (ctx, api) => needActive(ctx) && go(ctx, api, "wM_KanJub"),
|
|
Act_CalBoq: (ctx, api) => needActive(ctx) && api.cascadeAndRender(),
|
|
Act_Calc_Hae: (ctx, api) => api.cascadeAndRender(),
|
|
Act_Prn: (ctx, api) => go(ctx, api, "PM_Prt"),
|
|
ActMkCont00: stage("CONTRACT"),
|
|
Act_MkCont41: stage("EXEC"),
|
|
ActMkCont10: stage("GISUNG"),
|
|
ActMkCont21: stage("REVISION"),
|
|
ActMkCont20: stage("REVISION"),
|
|
ActHae_Arc: (ctx, api) => api.saveAll(),
|
|
Act_St8_XML: (ctx, api) => go(ctx, api, "PM_Prt"),
|
|
Act_St8_Jodal1: (ctx, api) => go(ctx, api, "PM_Prt"),
|
|
Act_Find: (ctx, api) => {
|
|
const kw = prompt("찾을 문자열(공사명·공사번호)", ctx.uiState.prjFind || "");
|
|
if (kw == null) return;
|
|
ctx.uiState.prjFind = kw.trim();
|
|
api.renderWorkspace();
|
|
},
|
|
Act_View_BOQ: (ctx, api) => ((ctx.uiState.showBoqPreview = ctx.uiState.showBoqPreview === false), api.renderWorkspace()),
|
|
ActIndex: (ctx, api) => ((ctx.uiState.prjDesc = !ctx.uiState.prjDesc), sortBy("PCODE", ctx.uiState.prjDesc)(ctx, api)),
|
|
Act_Indx_PCODE: sortBy("PCODE"),
|
|
Act_Indx_PNAME: sortBy("PNAME"),
|
|
Act_Indx_PNUM: sortBy("PNUM"),
|
|
Act_List_UP: moveSel(-1),
|
|
Act_List_Down: moveSel(1),
|
|
};
|
|
const enabled = (act) => Boolean(P.actions[act] || H[act]);
|
|
const run = (act, ctx, api, btn) => (P.actions[act] ? P.actions[act](ctx, api, btn) : H[act] && H[act](ctx, api));
|
|
|
|
function previewRows(ctx, code) {
|
|
if (code === activeCode(ctx)) return global.STMATE_VIEW_PRJ_BOQ.evaluateFullBoqRows(ctx);
|
|
return ((ctx.dbfData.boq_tables || {})[`BOQ${code}.DBF`] || []).map((r) => Object.assign({}, r));
|
|
}
|
|
|
|
function renderList(ctx, api) {
|
|
if (!ctx.dbfData) return renderMgr(ctx);
|
|
const kw = String(ctx.uiState.prjFind || "").toLowerCase();
|
|
const rows = prjRows(ctx).filter((r) => !kw || `${r.PNUM} ${r.PNAME}`.toLowerCase().includes(kw));
|
|
const cur = selRow(ctx),
|
|
code = cur ? String(cur.PCODE).trim() : "";
|
|
const act = activeCode(ctx);
|
|
const { fmt } = api;
|
|
const bt = ctx.computed.boqComputed.totals,
|
|
kj = ctx.computed.kanjubComputed;
|
|
const show = ctx.uiState.showBoqPreview !== false;
|
|
const prev = show ? previewRows(ctx, code) : [];
|
|
const menu = `<div class="sub-menubar">${PRJ_MENU.map(
|
|
([cap, items]) =>
|
|
`<div class="menu-item"><span>${cap}</span><div class="menu-dropdown">${items
|
|
.map((x) => (x ? `<button data-prj-act="${x[0]}" ${enabled(x[0]) ? "" : `disabled title="${DISABLED}"`}>${esc(x[1])}</button>` : "<hr />"))
|
|
.join("")}</div></div>`,
|
|
).join("")}</div>`;
|
|
const btn = (a, t) => `<button class="win-btn" data-prj-act="${a}" ${enabled(a) ? "" : `disabled title="${DISABLED}"`}>${t}</button>`;
|
|
return `<div class="win-form" id="prj-form">
|
|
<div class="win-form-header"><span>공사목록표 [wPrjList · PRJLST.DBF] — ${prjRows(ctx).length}건</span><span>${esc(ctx.currentSourcePath)}</span></div>
|
|
${menu}
|
|
<div class="win-subbar">${btn("Act_ED_PNUM", "등록정보 수정")}${btn("Act_Ed_Jab", "제잡비 계산")}${btn("ActEditBoq", "해당분 수정")}${btn("ActAddCopy", "복 사 등 록")}${btn("ActIndex", ctx.uiState.prjDesc ? "올림차순" : "내림차순")}${btn("ActHae_Arc", "해당분만 압축하기")}${btn("Act_View_BOQ", show ? "미리보기 숨기기" : "내역서 미리보기")}${kw ? `<span class="stm-note">찾기: ${esc(kw)}</span>` : ""}</div>
|
|
<div class="split-vert">
|
|
<div class="pane-box" style="flex:${show ? "0 0 32%" : "1"};"><div class="grid-scroll"><table class="dbgrid"><thead><tr><th>공사번호</th><th>공 사 명</th><th>코드</th><th>공사종류</th><th>직공비</th><th>총공사비</th><th>복사원본</th><th>지금 계산(직공비 / 총공사비)</th></tr></thead><tbody>
|
|
${rows
|
|
.map((r) => {
|
|
const c = String(r.PCODE).trim();
|
|
const live = c === act ? `${fmt(bt.directT)} / ${fmt(kj.GRAND_TOTAL)}` : "";
|
|
return `<tr class="prj-pick ${c === code ? "boq-sel" : ""}" data-code="${esc(c)}"><td>${esc(r.PNUM)}</td><td><strong>${esc(r.PNAME)}</strong></td><td class="center">${esc(c)}${c === act ? " ●" : ""}</td><td class="center">${esc(r.PCLASS)} · 차수 ${esc(r.K_ORD)}</td><td class="num">${fmt(num(r.TCOST1))}</td><td class="num">${fmt(num(r.COSTW1))}</td><td class="center">${num(r.CCODE) ? esc(r.CCODE) : ""}</td><td class="num">${live}</td></tr>`;
|
|
})
|
|
.join("")}</tbody></table></div></div>
|
|
${
|
|
show
|
|
? `<div class="pane-box" style="flex:1;"><div class="pane-title"><span>BOQ${esc(code)}.DBF 미리보기 — ${prev.length}행 (이 화면에서는 편집할 수 없음)</span><span>${code === act ? "지금 계산 금액" : "파일 저장 금액"}</span></div><div class="grid-scroll"><table class="dbgrid"><thead><tr><th>공종</th><th>명 칭</th><th>규 격</th><th>수 량</th><th>단위</th><th>단가</th><th>금액</th><th>비고</th></tr></thead><tbody>
|
|
${prev
|
|
.map(
|
|
(r) =>
|
|
`<tr class="${r._rowClass || ""}"><td class="center">${esc(r.ITNUM)}</td><td>${esc(r.NAME1)}</td><td>${esc(r.SIZE1)}</td><td class="num">${num(r.QTY1) ? fmt(num(r.QTY1), 4) : ""}</td><td class="center">${esc(r.UNIT1)}</td><td class="num">${num(r.TAMT1) ? fmt(num(r.TAMT1)) : ""}</td><td class="num">${num(r.T_AMT1) ? fmt(num(r.T_AMT1)) : ""}</td><td class="center">${esc(String(r.BDCODE1 || "").trim())}</td></tr>`,
|
|
)
|
|
.join("")}</tbody></table></div></div>`
|
|
: ""
|
|
}
|
|
</div>
|
|
<div class="win-subbar"><span>PrjBar — 선택: ${esc(cur ? cur.PNAME : "")} (BOQ${esc(code)}) · 열린 내역서 BOQ${esc(act)}</span></div>
|
|
</div>`;
|
|
}
|
|
|
|
function bindList(ctx, api) {
|
|
if (!ctx.dbfData) return bindMgr(ctx, api);
|
|
if (!ctx.uiState.prjSel) ctx.uiState.prjSel = activeCode(ctx);
|
|
document.querySelectorAll("#prj-form [data-prj-act]").forEach((b) => (b.onclick = () => !b.disabled && run(b.getAttribute("data-prj-act"), ctx, api, b)));
|
|
document.querySelectorAll("#prj-form .prj-pick").forEach((tr) => {
|
|
tr.onclick = () => ((ctx.uiState.prjSel = tr.getAttribute("data-code")), api.renderWorkspace());
|
|
tr.ondblclick = () => ((ctx.uiState.prjSel = tr.getAttribute("data-code")), run("ActEditBoq", ctx, api));
|
|
});
|
|
}
|
|
|
|
P.screens.wPrjMgr = { render: renderMgr, bind: bindMgr };
|
|
if (!P.screens.wPrjList) P.screens.wPrjList = { render: renderList, bind: bindList };
|
|
P.screens.wQuit = {
|
|
render: () =>
|
|
`<div class="win-form"><div class="win-form-header"><span>시스템종료</span></div><div class="stm-quit"><p>STMATE 작업을 마쳤음.</p><p><button class="win-btn primary" id="quit-back">다시 시작 — 프로젝트 관리</button></p></div></div>`,
|
|
bind: (ctx, api) => ($("quit-back").onclick = () => go(ctx, api, "wPrjMgr")),
|
|
};
|
|
})(window);
|