/* ============================================================================= * M02_MasterTemplete_Hopyo_Compare.ts * 불러오기 고름 창 — 후보 블록 줄 비교 · M01 요소 후보(PLAN 746-2 · 요구 585 · 서버 746-1 `줄비교` · `후보`) — * · `compareView` = 고름 대기 후보(앞 셋)마다 줄 비교 표(줄 · 엑셀 품 · 기초 품 · 꼴 배지) + 못옮김 수 · 후보 바꿈 단추 * · `elemChoices` = 못옮김 · 안 이은 기계 줄의 M01 요소 후보 셋 단추(누르면 부르는 쪽이 이음) * 보이기만 — 판정 · 순위 · 미리 고름은 서버 그대로 · 고르기는 부르는 쪽. * ========================================================================== */ import { createBadge, type BadgeTone } from "@ui/ui_template_badge"; import { createButton, el } from "@ui/ui_template_elements"; import { t as L, type LocaleKey } from "@ui/ui_template_locale"; import { createSegment } from "@ui/ui_template_segment"; import { createTable } from "@ui/ui_template_table"; import { createText } from "@ui/ui_template_text"; import type { CompareLook, ElemCand, SangeunCand, SangeunCompare, } from "./M02_MasterTemplete_Hopyo_Api"; const LOOK: Record = { 같음: { tone: "success", key: "M02_CmpSame" }, 품다름: { tone: "warning", key: "M02_CmpDiff" }, 엑셀만: { tone: "info", key: "M02_CmpExcel" }, 기초만: { tone: "neutral", key: "M02_CmpBase" }, }; const LOOKS = Object.keys(LOOK) as CompareLook[]; const num = (n: number | null): string => (n === null ? "—" : String(Number(n.toPrecision(4)))); /** 선택 칸 글 뒤 요약 — 「같음 3 · 품 다름 1」 · 견줌 못함 = "" */ export const compareBrief = (c: SangeunCompare | null | undefined): string => c ? LOOKS.filter((k) => c.수[k]) .map((k) => `${L(LOOK[k].key)} ${c.수[k]}`) .join(" · ") : ""; /** 줄 비교 표 하나 */ function compareTable(c: SangeunCompare): HTMLElement { const rows = c.줄.map((x) => [ `${x.갈래} ${x.이름}${x.규격 ? ` ${x.규격}` : ""}`, `${num(x.엑셀)} ${x.단위 ?? ""}`.trim(), `${num(x.기초)} ${x.단위 ?? ""}`.trim(), createBadge(L(LOOK[x.꼴].key), LOOK[x.꼴].tone), ]); return createTable( [ { label: L("M02_SangeunColName") }, { label: L("M02_CmpColExcel"), width: "112px", align: "end" }, { label: L("M02_CmpColBase"), width: "112px", align: "end" }, { label: L("M02_CmpColLook"), width: "84px" }, ], rows, { density: "data", empty: L("M02_CmpNone") }, ); } /** * 고름 대기 후보 줄 비교 칸 — 앞 후보 줄 비교를 후보 단추로 바꿔 봄 · `show(로직)` 으로 밖(고르기 칸)이 바꿈. * 줄 비교 있는 후보가 하나도 없으면 null. */ export function compareView( cands: SangeunCand[], ): { root: HTMLElement; show: (로직: string) => void } | null { const has = cands.filter((c) => c.줄비교); if (!has.length) return null; const root = el("div", { className: "m02-hopyo__cmp", attrs: { "data-cmp": "" } }); const body = el("div"); const seg = createSegment({ options: has.map((c, i) => ({ value: c.로직, label: `${i + 1}. ${c.이름}`, title: c.이름 })), value: has[0]!.로직, size: 24, ariaLabel: L("M02_CmpTitle").replace("{name}", ""), onChange: (v) => paint(v), }); const paint = (로직: string): void => { const c = has.find((x) => x.로직 === 로직) ?? has[0]!; const cmp = c.줄비교!; body.dataset.cmpOf = c.로직; const lost = cmp.못옮김.length; body.replaceChildren( createText( [ L("M02_CmpTitle").replace("{name}", c.이름), compareBrief(cmp), lost ? L("M02_CmpLost").replace("{n}", String(lost)) : "", ] .filter(Boolean) .join(" · "), "muted", 12, ), compareTable(cmp), ); }; root.append(seg.root, body); paint(has[0]!.로직); return { root, show: (로직) => { if (!has.some((c) => c.로직 === 로직)) return; seg.set(로직); paint(로직); }, }; } /** 요소 후보 단추 줄(셋까지) — 누르면 `pick(후보)` */ export function elemChoices(cands: ElemCand[], pick: (c: ElemCand) => void): HTMLElement | null { if (!cands.length) return null; const box = el("div", { className: "ui-toolbar", attrs: { "data-elem": "" } }); box.title = L("M02_ElemTitle"); for (const c of cands.slice(0, 3)) { const price = c.단가 ? ` · ${c.단가.toLocaleString()}` : ""; const b = createButton({ label: `${c.이름}${c.규격 ? ` ${c.규격}` : ""} [${c.단위 ?? ""}]${price}`, size: 24, variant: "ghost", onClick: () => pick(c), }); b.dataset.elemKey = c.키; b.title = `${c.키} · ${c.갈래}`; box.append(b); } return box; }