const EstReport = (() => {
let catalog = [];
function setting() {
const project = EstApp.state.project,
settings = project.printSettings || {};
return {
form: "current",
paper: "A4",
orientation: "landscape",
margin: 12,
font: 9,
company: "",
startPage: 1,
rowsPerPage: 30,
notes: "",
...settings,
};
}
function preview(all = false) {
const { state, esc } = EstApp;
const settings = setting(),
form = catalog.find((row) => String(row.Index) === settings.form),
output = form ? EstForms.data(form.Index, state.project) : null;
const number = (value) =>
new Intl.NumberFormat("ko-KR", { maximumFractionDigits: 12 }).format(Number(value) || 0);
const root = document.createElement("div");
if (output) root.innerHTML = EstForms.grid(output);
else if (form) {
const total = state.project.calculation?.total || 0;
root.innerHTML = `
${esc(settings.notes)}
${[8, 9].includes(form.Index) ? `공사금액 ${number(total)}원
${esc(settings.company)}
` : ""}`;
} else if (state.project.tables[state.view]) {
const rows = all
? state.project.tables[state.view].map((row, index) => ({ row, index }))
: EstApp.visibleRows();
const common = ["Code", "Name", "Size", "Unite"];
const columns = {
EstMainDat: [...common, "GN", "TU", "TC", "NC", "GC", "JC", "SC"],
HeadMainDat: [...common, "GN", "NU", "GU", "JU", "SU", "TU", "NC", "GC", "JC", "SC", "TC"],
CostMainDat: ["LineData"],
MachMainDat: [...common, "GN", "TU", "NC", "GC", "JC", "TC"],
Wonga: ["Gubun", "Name", "Youl", "VSusic", "Cost"],
};
root.innerHTML = EstViews.grid(
state.view,
rows,
columns[state.view] ||
EstModel.columns(state.view).filter(
(key) =>
![
"ValueIndex",
"SelectField",
"Ratio",
"NachalRatio",
"Diget",
"DigetIndex",
].includes(key),
),
);
} else root.innerHTML = EstApp.byId("work-content").innerHTML;
root
.querySelectorAll("button,.row-number,.business-tools,select#quantity-sheet")
.forEach((node) => node.remove());
root.querySelectorAll("input,textarea,select").forEach((node) => {
const value =
node.tagName === "SELECT"
? node.selectedOptions[0]?.textContent || ""
: node.type === "number" && node.value !== ""
? number(node.value)
: EstText.clean(EstText.encode(node.value));
if (node.tagName === "TEXTAREA") node.parentElement.classList.add("col-LineData");
if (node.type === "number") node.parentElement.classList.add("numeric");
node.replaceWith(document.createTextNode(value));
});
root
.querySelectorAll("[contenteditable]")
.forEach((node) => node.removeAttribute("contenteditable"));
const title = form?.Name2 || EstApp.titles[state.view] || state.view;
const detail =
state.view === "quantity"
? state.project.quantitySheets?.find((sheet) => sheet.Code === state.quantitySheet)?.Name
: state.view === "progress"
? state.project.progress.find((round) => round.id === state.roundId)?.name
: state.group
? EstNative.groups(state.project.tables[state.view] || []).get(state.group)?.rows[0]
?.Name
: "";
const totals = state.project.calculation;
const errors = [...(totals?.errors || []), ...(output?.errors || [])];
const source = `${esc(state.project.name)} · ${esc(title)}
${esc(state.project.name)}
${esc(title)}
${detail ? `${esc(detail)}
` : ""}${totals ? `순공사비 ${number(totals.direct?.TC)}원 · 도급액 ${number(totals.total)}원
` : ""}
${paginate(root, settings)}${errors.length ? `계산 오류 ${errors.length}건: ${esc(errors.join(" / "))}
` : ""}`;
EstApp.byId("print-frame").srcdoc = source;
}
function paginate(root, settings) {
const table = root.querySelector("table");
if (!table || root.querySelectorAll("table").length !== 1) return root.innerHTML;
const rows = [...table.querySelectorAll("tbody>tr")],
pages = [];
for (let at = 0; at < rows.length; at += settings.rowsPerPage) {
const copy = table.cloneNode(false);
copy.innerHTML = table.querySelector("thead")?.outerHTML || "";
const body = document.createElement("tbody");
rows.slice(at, at + settings.rowsPerPage).forEach((row) => body.append(row.cloneNode(true)));
copy.append(body);
pages.push(
``,
);
}
return pages.join("");
}
async function open() {
const defaults = await EstApp.api("/api/print-catalog");
const original = new Map(
(EstApp.state.project.printCatalog || []).map((row) => [row.Index, row]),
);
catalog = defaults.map((row) => ({
...row,
...original.get(row.Index),
}));
const table = EstApp.state.project.tables[EstApp.state.view];
const s = setting(),
{ esc } = EstApp;
EstApp.showDialog(
"인쇄 미리보기",
`
${[
["margin", "여백(mm)", 0, 50],
["font", "글자(pt)", 6, 20],
["rowsPerPage", "페이지당 행", 1, 100],
["startPage", "시작 쪽", 1, 99999],
]
.map(
([key, label, min, max]) =>
``,
)
.join("")}
${table ? '' : ""}
`,
);
EstApp.byId("dialog-content")
.querySelectorAll("[data-print],#print-paper,#print-orientation,#print-form")
.forEach((node) =>
node.addEventListener("change", () => {
const key = node.dataset.print || node.id.slice(6),
value = node.type === "number" ? Number(node.value) : node.value;
try {
if (
node.type === "number" &&
(!Number.isFinite(value) ||
value < Number(node.min) ||
value > Number(node.max) ||
(["rowsPerPage", "startPage"].includes(key) && !Number.isInteger(value)))
)
throw Error("인쇄 설정 범위 오류");
EstApp.change(() => {
const next = { ...setting(), [key]: value };
if (key === "form") {
const found = catalog.find((row) => String(row.Index) === value);
const original = found && {
...found,
...EstApp.state.project.nativePrintChanges?.[value],
};
if (original) {
next.rowsPerPage = Math.max(
1,
Math.min(100, Number(original.PrintLineCount) || 30),
);
if (Number.isFinite(original.PrintLeft))
next.margin = Math.max(0, Math.min(50, original.PrintLeft));
}
}
if (key === "notes") {
EstApp.state.project.documents ||= {};
EstApp.state.project.documents[next.form] = value;
}
if (
["margin", "rowsPerPage"].includes(key) &&
next.form !== "current" &&
EstApp.state.project.printCatalog?.some((row) => String(row.Index) === next.form)
) {
EstApp.state.project.nativePrintChanges ||= {};
const changes = (EstApp.state.project.nativePrintChanges[next.form] ||= {});
if (key === "margin")
for (const side of ["PrintTop", "PrintBottom", "PrintLeft", "PrintRight"])
changes[side] = value;
else changes.PrintLineCount = value;
}
if (key === "form") next.notes = EstApp.state.project.documents?.[value] || "";
EstApp.state.project.printSettings = next;
}, false);
if (key === "form") {
const content = EstApp.byId("dialog-content"),
current = EstApp.state.project.printSettings;
for (const name of ["notes", "margin", "rowsPerPage"])
content.querySelector(`[data-print=${name}]`).value = current[name];
}
preview(EstApp.byId("print-scope")?.value === "all");
EstApp.byId("print-error").textContent = "";
} catch (error) {
EstApp.byId("print-error").textContent = error.message;
node.value = setting()[key];
}
}),
);
EstApp.byId("print-scope")?.addEventListener("change", (event) =>
preview(event.target.value === "all"),
);
preview();
}
function print() {
const frame = EstApp.byId("print-frame");
if (!frame?.contentDocument?.body) throw Error("인쇄 미리보기가 준비되지 않았습니다.");
frame.contentWindow.focus();
frame.contentWindow.print();
}
return { open, preview, print };
})();