feat(est-test): 하단으로 집계·공종 다듬기·들여쓰기·레벨 확인
- 원본 처리 근거대로 소계 줄 넣기·지우기, 내역 엔진에 소계 행 합 추가 - 공종 들여쓰기, 단가산출 들여쓰기, 단가산출 레벨 나무 보기 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""EST 테스트 앱 하단으로 집계·공종 다듬기·단가산출 들여쓰기·레벨 확인(원본 f01a90·0x113eb44·0xf0770c·0xd10840)."""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
FILES = [
|
||||
"native-math.js",
|
||||
"native-formula.js",
|
||||
"native-machine.js",
|
||||
"native-rows.js",
|
||||
"native-text.js",
|
||||
"native-quantity.cjs",
|
||||
"native-wonga.js",
|
||||
"native-engine.js",
|
||||
"native-row-match.js",
|
||||
"native-escalation.js",
|
||||
"native-progress.js",
|
||||
"native-business.js",
|
||||
"native-review.js",
|
||||
"native-resources.js",
|
||||
"native-schedule.js",
|
||||
"native-model.js",
|
||||
"native-verify.js",
|
||||
"native-est-tools.js",
|
||||
]
|
||||
|
||||
|
||||
def run_js(code: str):
|
||||
runner = f"""
|
||||
const fs = require('fs'), vm = require('vm');
|
||||
const ctx = vm.createContext({{ structuredClone, console }});
|
||||
for (const f of {json.dumps(FILES)})
|
||||
vm.runInContext(fs.readFileSync('tools/est-test/' + f, 'utf8'), ctx, {{ filename: f }});
|
||||
ctx.source = JSON.parse(fs.readFileSync('tools/est-test/sample.json', 'utf8'));
|
||||
process.stdout.write(JSON.stringify(vm.runInContext({json.dumps(code)}, ctx)));
|
||||
"""
|
||||
return json.loads(subprocess.check_output(["node", "-e", runner], cwd=ROOT).decode("utf-8"))
|
||||
|
||||
|
||||
def test_down_sum_inserts_nested_subtotals_and_engine_fills_them():
|
||||
result = run_js(
|
||||
"""(() => {
|
||||
const calc = (p) => EstNative.calculate(EstModel.validate(p));
|
||||
const p = calc(structuredClone(source));
|
||||
const total = p.calculation.total, direct = p.calculation.direct.TC;
|
||||
const added = EstEstTools.downSum(p);
|
||||
const again = EstEstTools.downSum(p);
|
||||
const done = calc(p);
|
||||
const rows = done.tables.EstMainDat;
|
||||
const subs = rows.map((r, i) => [r, i]).filter(([r]) => Number(r.LinkIndex) === 53);
|
||||
const first = subs[0][1];
|
||||
let head = first - 1; while (head >= 0 && !(Number(rows[head].Level) > 0)) head--;
|
||||
const expected = rows.slice(head + 1, first).filter((r) => !Number(r.Level)).reduce((n, r) => n + Number(r.TC || 0), 0);
|
||||
const removed = EstEstTools.upSum(done);
|
||||
return { added: added > 0, again, firstOk: Math.abs(Number(rows[first].TC) - expected) < 1 && expected > 0,
|
||||
name: rows[first].Name, total: done.calculation.total === total, direct: done.calculation.direct.TC === direct,
|
||||
removed: removed === added, errors: done.calculation.errors.length };
|
||||
})()"""
|
||||
)
|
||||
assert result == {
|
||||
"added": True,
|
||||
"again": 0,
|
||||
"firstOk": True,
|
||||
"name": "소 계",
|
||||
"total": True,
|
||||
"direct": True,
|
||||
"removed": True,
|
||||
"errors": 0,
|
||||
}
|
||||
|
||||
|
||||
def test_left_space_indent_and_level_tree():
|
||||
result = run_js(
|
||||
"""(() => {
|
||||
const p = { options: {}, tables: { EstMainDat: [
|
||||
{ Level: 3, Name: 'A' }, { Level: 2, Name: 'B' }, { Level: 0, Name: 'b1' }, { Level: 2, Name: 'C' },
|
||||
{ Level: 1, Name: 'D' }, { Level: 0, Name: 'd1' }, { Level: 3, Name: 'E' }, { LinkIndex: 54, Level: 0 } ],
|
||||
CostMainDat: [
|
||||
{ Index: 1, Code: 'D1', Name: '터파기', Size: '토사' },
|
||||
{ LineData: ' 가 = \x123\x12' },
|
||||
{ LineData: ' 1', Level: 0 }, { LineData: ' ───── = \x120.5\x12' }, { LineData: ' 2' },
|
||||
{ Level: 2, LineData: ' 큰 공종' },
|
||||
{ SCheck: -1, LineData: '노무 \x1212,345\x12' } ] } };
|
||||
EstEstTools.leftSpace(p, 2);
|
||||
EstEstTools.costIndent(p, 4);
|
||||
const tree = EstEstTools.levelTree(p, 'D1');
|
||||
return { left: p.tables.EstMainDat.map((r) => r.LeftSpace), lines: p.tables.CostMainDat.map((r) => r.LineData),
|
||||
tree: tree.title, items: tree.lines.map((l) => l.text.replace(/ {2,}/g, '_')) };
|
||||
})()"""
|
||||
)
|
||||
assert result["left"] == [0, 2, 4, 2, 4, 6, 0, 0]
|
||||
assert result["lines"][1] == " 가 = \x123\x12"
|
||||
assert result["lines"][2:5] == [" 1", " ───── = \x120.5\x12", " 2"]
|
||||
assert result["lines"][5] == " 큰 공종"
|
||||
assert result["tree"] == "터파기 (토사)"
|
||||
assert result["items"] == ["큰 공종_[2]", "노무비 : 12,345"]
|
||||
@@ -495,6 +495,8 @@ ative-standard.js에 표준시장단가 원천(SilJukDat)과 내역 행(EstMainD
|
||||
- [x] 데이타 자동연결(원본 ActionAutoLink 0x10d54f4·TfrmDataAutoLink 0x10d3008·10d2a48·맞춤 10e0e58 — 디스어셈블 근거) — 9개 선택·Over Write, 정규화·맞춤 단계·노임 맞바꿈·기계 주연료·건너뛰기 규칙·쓰는 값 원본대로, 전체 순서 노임→자재→견적→표준→기계(내역·일위만)→단가산출→일위대가.
|
||||
- 검증완료: `test_est_autolink.py`(노임 품명만·규격 재시도 맞바꿈·단위 정규화·일위 비목 단가·SCheck 3·Over Write·이미 연결 건너뜀·전체 끊기) 통과. ORCA 브라우저에서 샘플 전체자동(Over Write) → 7행 연결·순공사비 45,293,066 그대로, 되돌리기 원상.
|
||||
- 남음: 전역변수(`` 표지 — 설치 자료 사용 0건, 단가산출 엔진 깊은 변경이 필요해 보류).
|
||||
- [x] 하단/상단으로 집계·공종 다듬기·단가산출 들여쓰기·레벨 확인(원본 f01a90·f018d0·f01750, 0x113eb44, 0xf0770c, 0xd10840 — 디스어셈블 근거) — 「소 계」(53, Level −10) 넣기 규칙, 내역 엔진에 53 행 합(닫는 공종 안쪽부터, 상세 행) 추가, LeftSpace 계산·이름 칸 들여 보이기, 들여쓰기 n 칸·분수 세 줄 함께, 레벨 나무(60−Level 칸 [레벨]·비목 소계). 다시 집계 시 겹침 방지는 웹 보완.
|
||||
- 검증완료: `test_est_est_tools.py` 2개(소계 넣기·재실행 0·첫 소계 = 공종 합·도급액/순공사비 불변·상단 집계로 원상, 들여쓰기·LeftSpace·레벨 나무) 통과, 대조·업무·설계변경·연차·ESTX 저장 시험 111개 통과(엔진 변경 회귀). ORCA 브라우저에서 하단 집계 11줄(토공 2,382,484 등 공종 합과 같음)·도급액 유지, 공종 다듬기 59칸 들여쓰기, 레벨 나무 표시, 샘플 복구.
|
||||
- 대조 합계(CAD 제외 882개): 구현 283 · 부분 193 · 없음 273 · 제외 133.
|
||||
- [ ] 남은 핵심(실무 빈도순):
|
||||
3. 수량산출서 — 누계·4방향 합계, 제출용 전체 인쇄.
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
내역서·일위대가 화면의 `편집 → 단가 고르기`는 자재·노임·기계경비·일위대가·단가산출·견적·실적 자료를 검색어로 찾아 선택 행 아래에 넣거나, 선택 행 하나의 연결을 바꿉니다. 여러 개를 골라 한 번에 넣을 수 있고, 바꿀 때는 변경 전·후를 확인한 뒤 같은 코드를 쓰는 이 표의 다른 행도 함께 바꿀지 고릅니다. 넣거나 바꾼 행만 원천 단가를 다시 읽습니다. 단가산출 산출근거 행은 대상이 아닙니다.
|
||||
|
||||
`편집 → 하단으로 집계`는 원본처럼 공종이 끝나는 자리(다음 공종 머리·순공사비 앞·마지막)에 「소 계」 줄을 넣고, 계산할 때 그 공종 상세 행의 합을 채웁니다. 겹쳐 넣지 않으며 `상단으로 집계`는 소계 줄을 지웁니다. `공종 다듬기`는 레벨마다 공종명을 들여 보입니다. `단가산출 들여쓰기`는 산출근거 줄 앞 빈칸을 같은 칸 수로 맞추고(분수 세 줄은 함께 옮김), `단가산출 레벨 확인`은 현재 호표의 레벨 줄과 비목 소계를 계층으로 보입니다.
|
||||
|
||||
`편집 → 데이타 자동연결`은 원본처럼 내역서·일위대가·기계경비 행을 현재 공사의 노임·자재·견적·표준·기계경비·단가산출·일위대가 표와 품명(·규격·단위)으로 맞춰 코드·단가·연결종류를 적습니다. 빈칸·|·괄호를 무시하고 단위의 「당」·[ ]를 지우며, 노임은 품명만 맞추고 못 찾으면 규격으로 찾아 품명·규격을 맞바꿉니다. `Over Write`를 끄면 이미 연결된 행은 건너뜁니다. `전체연결 끊기`는 원본처럼 모든 행의 연결종류를 0으로 합니다.
|
||||
|
||||
`편집 → 선택 행 자재단가 등록`·`견적단가 등록`은 원본처럼 내역·일위대가 행을 새 자재(M)·견적(W) 단가로 등록하고 그 행을 새 코드에 연결합니다. 같은 품명·규격이 있으면 거부하고, 일위대가 호표 제목 줄은 등록하지 않습니다. `품명정리`는 고른 일위대가 호표의 품명을 커서 자리에서 나눠 뒤쪽을 규격 앞에 붙이고, 단위가 비어 있으면 채웁니다.
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<script defer src="native-xml-check.js"></script>
|
||||
<script defer src="native-review-auto.js"></script>
|
||||
<script defer src="native-autolink.js"></script>
|
||||
<script defer src="native-est-tools.js"></script>
|
||||
<script defer src="native-actions.js"></script>
|
||||
<script defer src="native-business-actions.js"></script>
|
||||
<script defer src="native-app.js"></script>
|
||||
@@ -140,6 +141,11 @@
|
||||
<button data-action="bid-rates">낙찰률 적용</button
|
||||
><button data-action="restore-bid">당초단가(100%) 복원</button>
|
||||
<hr />
|
||||
<button data-action="est-down-sum">하단으로 집계(소계 넣기)</button
|
||||
><button data-action="est-up-sum">상단으로 집계(소계 지우기)</button
|
||||
><button data-action="est-left-space">공종 다듬기</button
|
||||
><button data-action="cost-indent">단가산출 들여쓰기</button
|
||||
><button data-action="cost-level-check">단가산출 레벨 확인</button>
|
||||
<button data-action="tidy-sort">정렬(가나다, 기초단가)</button
|
||||
><button data-action="tidy-arrange">데이타 위치정리</button
|
||||
><button data-action="tidy-number">호표 정리(번호정렬)</button
|
||||
|
||||
@@ -617,7 +617,8 @@ const EstBusinessActions = (() => {
|
||||
(typeof EstEstimatePrint !== "undefined" && EstEstimatePrint.handle(name, state)) ||
|
||||
(typeof EstXmlCheck !== "undefined" && EstXmlCheck.handle(name, state)) ||
|
||||
(typeof EstReviewAuto !== "undefined" && EstReviewAuto.handle(name, state)) ||
|
||||
(typeof EstAutoLink !== "undefined" && EstAutoLink.handle(name, state))
|
||||
(typeof EstAutoLink !== "undefined" && EstAutoLink.handle(name, state)) ||
|
||||
(typeof EstEstTools !== "undefined" && EstEstTools.handle(name, state))
|
||||
) {
|
||||
EstApp.render();
|
||||
} else if (name === "load-contract" || name === "load-bid") {
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
// 원본 내역·단가산출 다듬기 도구.
|
||||
// 하단으로 집계(popMainDownSum → f01a90·f018d0): 공종 머리(TU 0 또는 57) 앞에서 열린 공종(레벨 1..L)마다, 순공사비(54)·경계(Index 59)
|
||||
// 앞과 마지막 행 뒤에서 열린 공종 전부에 「소 계」(LinkIndex 53, Level −10) 행을 넣는다. 바로 위 구간에 이미 소계가 있으면 넣지 않는다.
|
||||
// 금액은 재계산이 채운다(EstRows.estimate). 상단으로 집계(mnuSumUp f01750)는 53 행을 모두 지운다.
|
||||
// 이미 소계가 있는 자리는 닫힌 것으로 보아 다시 눌러도 겹치지 않게 한다(원본은 열린 표시가 남아 끝에 덧붙을 수 있음).
|
||||
// 공종 다듬기(mnuEstLeftSpace 0x113eb44): LeftSpace = 상위 공종 들여쓰기 + 간격, 54·55·58 행은 0.
|
||||
// 단가산출 들여쓰기(ActionLeftMargin 0xf0770c): 제목·레벨 행 말고 LineData 앞 빈칸을 n 칸으로 바꾸고, 분수(─) 세 줄은 같은 만큼 옮김.
|
||||
// 레벨 확인(TfrmCostLevelCheck 0xd10840): 현재 호표의 레벨 줄([레벨])과 비목 소계 줄을 계층으로 보인다(읽기 전용).
|
||||
const EstEstTools = (() => {
|
||||
const num = (value) => Number(value) || 0;
|
||||
|
||||
function hasSubtotalAbove(rows, at) {
|
||||
for (let i = at - 1; i >= 0; i--) {
|
||||
const r = rows[i];
|
||||
if (num(r.LinkIndex) === 53) return true;
|
||||
if (num(r.Level) > 0 || [54, 55, 58, 59].includes(num(r.LinkIndex))) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function downSum(project) {
|
||||
const rows = project.tables.EstMainDat || [],
|
||||
out = [],
|
||||
open = new Set();
|
||||
const subtotal = () => ({ Index: 0, Name: "소 계", LinkIndex: 53, Level: -10 });
|
||||
let added = 0;
|
||||
// 바로 위에 소계가 이미 있으면 넣지 않고 닫은 것으로 본다(다시 눌러도 겹치지 않게).
|
||||
const close = (max, at) => {
|
||||
const exists = hasSubtotalAbove(rows, at);
|
||||
for (let level = 1; level <= max; level++)
|
||||
if (open.has(level)) {
|
||||
open.delete(level);
|
||||
if (exists) continue;
|
||||
out.push(subtotal());
|
||||
added++;
|
||||
}
|
||||
};
|
||||
rows.forEach((row, i) => {
|
||||
const level = num(row.Level),
|
||||
link = num(row.LinkIndex);
|
||||
if (level > 0 && num(row.Index) !== 53) {
|
||||
if (!num(row.TU) || link === 57) close(level, i);
|
||||
open.add(level);
|
||||
} else if (link === 54 || num(row.Index) === 59) close(9, i);
|
||||
out.push(row);
|
||||
if (i === rows.length - 1) close(9, i + 1);
|
||||
});
|
||||
let pk = Math.max(0, ...rows.map((r) => num(r.PkID)));
|
||||
for (const row of out) if (row.PkID == null) row.PkID = ++pk;
|
||||
project.tables.EstMainDat = out;
|
||||
return added;
|
||||
}
|
||||
function upSum(project) {
|
||||
const rows = project.tables.EstMainDat || [];
|
||||
project.tables.EstMainDat = rows.filter((r) => num(r.LinkIndex) !== 53);
|
||||
return rows.length - project.tables.EstMainDat.length;
|
||||
}
|
||||
|
||||
function leftSpace(project, step) {
|
||||
if (!Number.isInteger(step) || step < 0 || step > 10)
|
||||
throw Error("들여쓰기 간격은 0~10 입니다.");
|
||||
const open = [];
|
||||
for (const row of project.tables.EstMainDat || []) {
|
||||
if ([54, 55, 58].includes(num(row.LinkIndex))) {
|
||||
row.LeftSpace = 0;
|
||||
continue;
|
||||
}
|
||||
const level = num(row.Level) > 0 ? num(row.Level) : 0;
|
||||
while (open.length && open.at(-1).level <= level) open.pop();
|
||||
const indent = open.length ? open.at(-1).indent + step : 0;
|
||||
row.LeftSpace = indent;
|
||||
if (level > 0) open.push({ level, indent });
|
||||
}
|
||||
project.options.EstLeftSpace = step;
|
||||
}
|
||||
|
||||
function costIndent(project, n) {
|
||||
if (!Number.isInteger(n) || n < 0 || n > 50) throw Error("들여쓰기 칸 수는 0~50 입니다.");
|
||||
const rows = project.tables.CostMainDat || [];
|
||||
const lead = (s) => String(s ?? "").length - String(s ?? "").trimStart().length;
|
||||
const shift = (s, delta) => {
|
||||
const text = String(s ?? "");
|
||||
return delta >= 0 ? " ".repeat(delta) + text : text.slice(Math.min(-delta, lead(text)));
|
||||
};
|
||||
const done = new Set();
|
||||
rows.forEach((row, i) => {
|
||||
if (done.has(i) || num(row.Index) > 0 || num(row.Level) > 0 || !row.LineData) return;
|
||||
if (String(row.LineData).includes("─")) {
|
||||
const delta = n - lead(row.LineData);
|
||||
for (const j of [i - 1, i, i + 1])
|
||||
if (rows[j] && !(num(rows[j].Index) > 0) && !done.has(j)) {
|
||||
rows[j].LineData = shift(rows[j].LineData, delta);
|
||||
done.add(j);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const next = rows[i + 1];
|
||||
if (next && String(next.LineData ?? "").includes("─")) return;
|
||||
row.LineData = " ".repeat(n) + String(row.LineData).trimStart();
|
||||
done.add(i);
|
||||
});
|
||||
}
|
||||
|
||||
function levelTree(project, code) {
|
||||
const rows = project.tables.CostMainDat || [],
|
||||
group = EstNative.groups(rows).get(code);
|
||||
if (!group) throw Error("레벨을 볼 단가산출 호표를 고르세요.");
|
||||
const head = group.rows[0],
|
||||
label = {
|
||||
[-5]: " 계 : ",
|
||||
[-3]: "경 비 : ",
|
||||
[-2]: "재료비 : ",
|
||||
[-1]: "노무비 : ",
|
||||
[-4]: "",
|
||||
};
|
||||
const lines = [];
|
||||
for (const row of group.rows.slice(1)) {
|
||||
const level = num(row.Level),
|
||||
plain = EstText.clean(row.LineData || "").trim();
|
||||
if (level > 0) {
|
||||
const s = plain.padEnd(104, " ");
|
||||
lines.push({ level, text: (s.slice(0, Math.max(0, 60 - level)) + `[${level}]`).trimEnd() });
|
||||
} else if (num(row.SCheck) <= -1 && num(row.SCheck) >= -5) {
|
||||
const value = String(row.LineData || "").match(/\x12([-\d.,]+)\x12/)?.[1] ?? "";
|
||||
lines.push({ level: 0, text: `${label[num(row.SCheck)]}${value}` });
|
||||
}
|
||||
}
|
||||
return { title: `${head.Name ?? ""} (${head.Size ?? ""})`, lines };
|
||||
}
|
||||
|
||||
function handle(name, state) {
|
||||
if (name === "est-down-sum" || name === "est-up-sum") {
|
||||
if (state.view !== "EstMainDat") throw Error("내역서 화면에서 집계하세요.");
|
||||
const down = name === "est-down-sum";
|
||||
if (
|
||||
!confirm(
|
||||
down ? "공사비 집계를 하단으로 하시겠습니까?" : "공사비 집계를 상단으로 하시겠습니까?",
|
||||
)
|
||||
)
|
||||
return true;
|
||||
let count;
|
||||
EstApp.change((project) => (count = down ? downSum(project) : upSum(project)));
|
||||
state.notice = down ? `소계 ${count}줄을 넣었습니다.` : `소계 ${count}줄을 지웠습니다.`;
|
||||
} else if (name === "est-left-space") {
|
||||
EstActions.form(
|
||||
"공종 다듬기",
|
||||
[
|
||||
{
|
||||
name: "step",
|
||||
label: "레벨당 들여쓰기 간격(0~10)",
|
||||
type: "number",
|
||||
value: state.project.options.EstLeftSpace ?? 2,
|
||||
},
|
||||
],
|
||||
({ step }) => EstApp.change((project) => leftSpace(project, step), false),
|
||||
);
|
||||
} else if (name === "cost-indent") {
|
||||
if (state.view !== "CostMainDat") throw Error("단가산출 화면에서 들여쓰기하세요.");
|
||||
EstActions.form(
|
||||
"들여쓰기",
|
||||
[{ name: "n", label: "들여쓰기 칸", type: "number", value: 4 }],
|
||||
({ n }) => EstApp.change((project) => costIndent(project, n)),
|
||||
);
|
||||
} else if (name === "cost-level-check") {
|
||||
if (state.view !== "CostMainDat") throw Error("단가산출 화면에서 레벨을 확인하세요.");
|
||||
const tree = levelTree(state.project, state.group),
|
||||
{ esc } = EstApp;
|
||||
EstApp.showDialog(
|
||||
"단가산출 레벨(계층) 보기",
|
||||
`<p><b>${esc(tree.title)}</b></p><pre style="max-height:420px;overflow:auto">${tree.lines.map((l) => esc(l.text)).join("\n") || "레벨 지정 줄이 없습니다."}</pre>`,
|
||||
);
|
||||
} else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return { downSum, upSum, leftSpace, costIndent, levelTree, handle };
|
||||
})();
|
||||
@@ -163,6 +163,7 @@ const EstRows = (() => {
|
||||
segment = [];
|
||||
continue;
|
||||
}
|
||||
if (link === 53) continue;
|
||||
if (link === 54) {
|
||||
Object.assign(row, add(prior));
|
||||
boundary = true;
|
||||
@@ -192,6 +193,19 @@ const EstRows = (() => {
|
||||
segment.push(row);
|
||||
if (!boundary) prior.push(row);
|
||||
}
|
||||
// 원본 하단으로 집계(popMainDownSum 0xf01a90)의 「소 계」 행(53): 닫는 공종(안쪽부터)의 상세 행 합.
|
||||
const open = [];
|
||||
output.forEach((row, i) => {
|
||||
const level = num(row.Level);
|
||||
if (level > 0) {
|
||||
while (open.length && open.at(-1).level <= level) open.pop();
|
||||
open.push({ level, start: i });
|
||||
} else if (num(row.LinkIndex) === 53) {
|
||||
const group = open.pop();
|
||||
const body = group ? output.slice(group.start + 1, i) : [];
|
||||
Object.assign(row, add(body.filter((r) => !num(r.Level) && num(r.LinkIndex) !== 53)));
|
||||
}
|
||||
});
|
||||
for (let i = output.length - 1; i >= 0; i--) {
|
||||
const row = output[i],
|
||||
level = num(row.Level);
|
||||
|
||||
@@ -76,7 +76,7 @@ const EstViews = (() => {
|
||||
}
|
||||
function grid(table, list, columns) {
|
||||
const { esc, number, state } = EstApp;
|
||||
return `<table class="sheet" data-grid="${table}"><thead><tr><th class="row-number">선택</th>${columns.map((key) => `<th class="col-${key}">${esc((table === "Wonga" && typeof EstJungSang !== "undefined" && EstJungSang.label(state.project, key)) || EstModel.labels[key] || key)}</th>`).join("")}</tr></thead><tbody>${list.map(({ row, index }) => `<tr data-index="${index}" class="${state.selection.has(index) ? "selected " : ""}${Number(row.Index) > 0 ? "heading " : ""}${Number(row.Level) > 0 ? "level " : ""}${row.Error ? "error-row" : ""}"><th class="row-number"><label><input type="checkbox" data-select="${index}" ${state.selection.has(index) ? "checked" : ""}>${index + 1}</label></th>${columns.map((key) => `<td class="col-${key}">${cell(table, index, key, row[key])}</td>`).join("")}</tr>`).join("")}</tbody></table>`;
|
||||
return `<table class="sheet" data-grid="${table}"><thead><tr><th class="row-number">선택</th>${columns.map((key) => `<th class="col-${key}">${esc((table === "Wonga" && typeof EstJungSang !== "undefined" && EstJungSang.label(state.project, key)) || EstModel.labels[key] || key)}</th>`).join("")}</tr></thead><tbody>${list.map(({ row, index }) => `<tr data-index="${index}" class="${state.selection.has(index) ? "selected " : ""}${Number(row.Index) > 0 ? "heading " : ""}${Number(row.Level) > 0 ? "level " : ""}${row.Error ? "error-row" : ""}"><th class="row-number"><label><input type="checkbox" data-select="${index}" ${state.selection.has(index) ? "checked" : ""}>${index + 1}</label></th>${columns.map((key) => `<td class="col-${key}"${key === "Name" && Number(row.LeftSpace) > 0 ? ` style="padding-left:${Number(row.LeftSpace) * 6}px"` : ""}>${cell(table, index, key, row[key])}</td>`).join("")}</tr>`).join("")}</tbody></table>`;
|
||||
}
|
||||
function render(state) {
|
||||
const { project, view } = state,
|
||||
|
||||
@@ -79,6 +79,7 @@ const allowed = new Set([
|
||||
"native-xml-check.js",
|
||||
"native-review-auto.js",
|
||||
"native-autolink.js",
|
||||
"native-est-tools.js",
|
||||
"native-issues.js",
|
||||
"native-attachments.js",
|
||||
"native-standard.js",
|
||||
|
||||
Reference in New Issue
Block a user