/* ============================================================================= * M02_MasterTemplete_Hopyo_ImportedPanel.ts * 가져온 식 단가산출 모달 머리 판(PLAN 793-3b · 요구 646 · 647 · 652) — `Hopyo_Imported` 가 엶 · 시트(`Sangeun_Detail`) 위. * · 줄 1 = 「새」 · 원본(파일 · 시트 · 행) · 검산 표지(같음 / 다름 n · 줄마다 까닭) · 단가 기준 [우리 단가로] / [엑셀 단가로] · * 바꾸기 전후 금액 · [원본 글 보기] * · 줄 2 = 이음 없는 자원 = 표지 + [이음 고르기] * · 줄 2-2 = 소요량 · 계수 이음(797-2 · 후보 집계 + [이음 보기] → `Hopyo_QtyLinks` 모달) * · 경쟁사 로직 그대로 — 기초로 바꾸기 · 근거 기초 제안 없음(809-1 · 요구 703) * 부품 = 단추 · 배지 · 글 · 입력 · 띠 · 표 · 모달(날 단추 · 입력 없음) · 서버 길은 부르는 쪽. * ========================================================================== */ import { createBadge } from "@ui/ui_template_badge"; import { createButton } from "@ui/ui_template_button"; import { el } from "@ui/ui_template_elements"; import { currentLanguageIndex } from "@ui/ui_template_locale"; import { openModal } from "@ui/ui_template_modal"; import { createTable } from "@ui/ui_template_table"; import { createText } from "@ui/ui_template_text"; import { createToolbar } from "@ui/ui_template_toolbar"; import { formatMoney } from "../common_util/common_util_money"; import { tallyBadges, type QtyTally } from "./M02_MasterTemplete_Hopyo_QtyLinks"; import { gapText, isImported, wrongLines, type ImportedDoc, type Money4, } from "./logic/M02_Hopyo_Imported_Model"; const TEXT = { Imported: ["가져온 식", "Imported formula"], Same: ["검산 같음", "Check matches"], Diff: ["검산 다름 {n}", "Check differs {n}"], NoCheck: ["검산 없음", "No check"], Basis: ["단가 기준", "Price basis"], ToOurs: ["우리 단가로", "Use our prices"], ToExcel: ["엑셀 단가로", "Use Excel prices"], Source: ["원본 글 보기", "Show source text"], SourceTitle: ["원본 글", "Source text"], Row: ["행", "Row"], Text: ["글", "Text"], Excel: ["엑셀", "Excel"], Now: ["지금", "Now"], Unlinked: ["이음 없는 자원 {n}", "Unlinked resources {n}"], Quote: ["견적 {n}", "Quotes {n}"], Link: ["이음 고르기", "Pick link"], Gap: ["바꾸기 전 {a} → 후 {b} ({d})", "Before {a} → after {b} ({d})"], QtyLinks: ["소요량 · 계수 이음 {n}", "Quantity / coefficient links {n}"], QtyOpen: ["이음 보기", "Show links"], Close: ["닫기", "Close"], } as const satisfies Record; export const tx = (k: keyof typeof TEXT): string => TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0]; export interface PanelState { doc: ImportedDoc; /** 이음 없는 자원(서버 price 답) */ unlinked: { 자리: string; 이름: string; 까닭?: string; 견적?: boolean }[]; /** 소요량 · 계수 이음 집계(서버 qty 답 · 797-2) */ qty: QtyTally | null; /** 마지막 단가 바꿈 전후 */ price: { 전: Money4 | null; 후: Money4 | null } | null; busy: boolean; readOnly: boolean; } export interface PanelActs { price: (basis: "엑셀" | "우리") => void; link: (spot: string, name: string) => void; qty: () => void; } const won = (m: Money4 | null | undefined): string => (m ? formatMoney(m.계) : "—"); const muted = (s: string): HTMLElement => createText(s, "muted", 14); /** [원본 글 보기] — 엑셀 글줄 · 엑셀 값 · 지금 셈 */ function showSource(doc: ImportedDoc): void { const src = doc.가져온?.원본; const lines = new Map((doc.가져온?.검산?.줄 ?? []).map((x) => [x.행, x])); const rows = (src?.글줄 ?? []).map((g) => { const hit = g.행 != null ? lines.get(g.행) : undefined; return [ String(g.행 ?? ""), g.글 ?? "", g.값 == null ? "" : formatMoney(g.값), hit?.셈 == null ? "" : formatMoney(hit.셈), hit ? createBadge(hit.같음 ? "=" : (hit.까닭 ?? "≠"), hit.같음 ? "success" : "danger") : "", ]; }); openModal({ title: `${tx("SourceTitle")} — ${[src?.파일, src?.시트].filter(Boolean).join(" · ")}`, closeLabel: tx("Close"), wide: true, dialogClass: "m02-modal m02-hopyo__src", mount: (body) => { const heads = [tx("Row"), tx("Text"), tx("Excel"), tx("Now"), ""]; const t = createTable(heads.map((h, i) => ({ label: h, ...(i > 1 && i < 4 ? { align: "end" as const } : {}) })), rows, { density: "data" }); // prettier-ignore t.dataset.importedSource = ""; body.append(t); }, }); } function checkBadge(doc: ImportedDoc): HTMLElement { const chk = doc.가져온?.검산; const bad = wrongLines(doc); const b = !chk ? createBadge(tx("NoCheck"), "neutral") : chk.같음 ? createBadge(tx("Same"), "success") : createBadge(tx("Diff").replace("{n}", String(bad.length || 1)), "danger"); b.dataset.importedCheck = chk ? String(chk.같음) : ""; b.title = [chk?.까닭, ...bad.map((x) => `${tx("Row")} ${x.행 ?? "?"}: ${x.까닭 ?? ""}`)] .filter(Boolean) .join("\n"); return b; } /** 머리 판 */ export function importedPanel(s: PanelState, a: PanelActs): HTMLElement { const meta = s.doc.가져온 ?? {}; const src = meta.원본; const here = isImported(s.doc); const head: (HTMLElement | "divider")[] = [ createBadge(tx("Imported"), "accent"), muted([src?.파일, src?.시트, src?.행?.join("~")].filter(Boolean).join(" · ")), checkBadge(s.doc), ]; if (here) { const ours = meta.단가기준 === "우리"; const flip = createButton({ label: ours ? tx("ToExcel") : tx("ToOurs"), size: 28, variant: "ghost", disabled: s.readOnly || s.busy, onClick: () => a.price(ours ? "엑셀" : "우리"), }); flip.dataset.importedPrice = ours ? "엑셀" : "우리"; head.push("divider", muted(`${tx("Basis")} ${meta.단가기준 ?? "엑셀"}`), flip); if (s.price) { const g = tx("Gap").replace("{a}", won(s.price.전)).replace("{b}", won(s.price.후)); const t = muted(g.replace("{d}", gapText(s.price.전, s.price.후))); t.dataset.importedGap = ""; head.push(t); } } const srcBtn = createButton({ label: tx("Source"), size: 28, variant: "ghost", disabled: !src?.글줄?.length, onClick: () => showSource(s.doc) }); // prettier-ignore srcBtn.dataset.importedSourceBtn = ""; head.push("divider", srcBtn); const kids: HTMLElement[] = [createToolbar({ items: head })]; if (here && s.unlinked.length) { const items = s.unlinked.map((u) => { const label = `${u.이름}${u.까닭 ? ` (${u.까닭})` : ""} · ${tx("Link")}`; // 못 이은 까닭(814-1) const b = createButton({ label, size: 24, variant: "ghost", disabled: s.readOnly || s.busy, onClick: () => a.link(u.자리, u.이름) }); // prettier-ignore b.dataset.importedLink = u.자리; return b; }); const quotes = s.unlinked.filter((u) => u.견적).length; // 815-1 견적 = 그대로 쓰는 값 · 못 이음 수에서 뺌 const left = s.unlinked.length - quotes; const tags = [ ...(left ? [createBadge(tx("Unlinked").replace("{n}", String(left)), "warning")] : []), ...(quotes ? [createBadge(tx("Quote").replace("{n}", String(quotes)), "info")] : []), ]; kids.push(createToolbar({ items: [...tags, ...items] })); } if (here && s.qty?.자리) { const open = createButton({ label: tx("QtyOpen"), size: 24, variant: "ghost", onClick: a.qty }); open.dataset.importedQty = String(s.qty.자리); const tag = createBadge(tx("QtyLinks").replace("{n}", String(s.qty.자리)), "info"); kids.push(createToolbar({ items: [tag, ...tallyBadges(s.qty), open] })); } return el("div", { className: "m02-std-sheet__body", attrs: { "data-imported-panel": "" }, children: kids }); // prettier-ignore }