/* ============================================================================= * E03_StabSection_UI_List.ts * E03 좌측 측점 목록(PLAN 532-3) — 측점 · 깎기/쌓기 · 조건별 최소 안전율 · 판정(아이콘 + 글) · * 대표 단면 별(자동 = 채움 · 사용자 켬/끔 = 밑줄 · 누르면 바꿈). 정렬 · 거름은 logic `listRows`. * ========================================================================== */ import { el } from "@ui/ui_template_elements"; import { t, type LocaleKey } from "@ui/ui_template_locale"; import { listRows, roleText, verdictOf, type Condition, type SortMode, type StationRow, } from "./logic/E03_StabSection_Logic"; const fmt = (v: number | null | undefined): string => v === null || v === undefined || !Number.isFinite(v) ? "-" : v.toFixed(2); export interface ListDraw { rows: StationRow[]; conditions: Condition[]; condition: Condition; sort: SortMode; ngOnly: boolean; active: string | null; /** 사용자 켬/끔 — 저장 전 캐시 포함 · 없으면 서버 값 */ representative: (row: StationRow) => { on: boolean; user: boolean | null }; editable: boolean; } export function buildList( draw: ListDraw, onSelect: (key: string) => void, onToggle: (key: string) => void, ): HTMLElement { const box = el("div", { className: "e03-list" }); const table = el("table", { attrs: { "data-role": "station-list" } }); const head = el("tr"); head.append( el("th", { text: t("E03_Col_Station") }), el("th", { text: t("E03_Col_Role") }), ...draw.conditions.map((c) => el("th", { className: "is-num", text: t(`E03_Cond_${c}` as LocaleKey), attrs: c === draw.condition ? { "aria-sort": "other" } : {}, }), ), el("th", { text: t("E03_Verdict") }), el("th", { text: t("E03_Col_Rep"), attrs: { title: t("E03_Rep_Hint") } }), ); table.append(el("thead", { children: [head] })); const body = el("tbody"); const rows = listRows(draw.rows, draw.condition, draw.sort, draw.ngOnly); for (const row of rows) { const tr = el("tr", { className: row.key === draw.active ? "is-active" : "", attrs: { "data-key": row.key, "data-verdict": verdictOf(row, draw.condition) }, }); const v = verdictOf(row, draw.condition); const rep = draw.representative(row); const star = el("button", { className: `e03-star${rep.on ? " is-on" : ""}${rep.user !== null ? " is-user" : ""}`, text: rep.on ? "★" : "☆", attrs: { type: "button", title: [ ...(row.representative?.auto ?? []).map((r) => t(`E03_Rep_${r}` as LocaleKey)), rep.user !== null ? t("E03_Rep_User") : "", ] .filter(Boolean) .join(" · ") || t("E03_Rep_Hint"), "aria-pressed": String(rep.on), }, }); star.disabled = !draw.editable; star.addEventListener("click", (ev) => { ev.stopPropagation(); onToggle(row.key); }); tr.append( el("td", { text: row.label?.replace(/^STA\./, "").replace(/\.000$/, "") ?? row.key, children: row.walls ? [el("span", { className: "e03-wall-tag", text: "▦", attrs: { title: t("E03_Walls") } })] : [], }), el("td", { text: row.sides.length ? roleText(row, { cut: t("E03_CutShort"), fill: t("E03_FillShort") }) : "-", }), ...draw.conditions.map((c) => el("td", { className: "is-num", text: fmt(row.min[c]) })), el("td", { children: [ el("span", { className: `e03-verdict e03-verdict--${v}`, text: row.error ? t("E03_Error") : v === "none" ? "-" : t(`E03_Verdict_${v}` as LocaleKey), }), ], }), el("td", { children: [star] }), ); if (row.error) tr.title = row.error; tr.addEventListener("click", () => onSelect(row.key)); body.append(tr); } table.append(body); box.append(table); if (!rows.length) box.append(el("p", { className: "e03-empty", text: t("E03_ListEmpty") })); queueMicrotask(() => box.querySelector("tr.is-active")?.scrollIntoView({ block: "nearest" })); return box; }