- 목록 판정 면 칸 · 결과 방법 줄 · 면 확인 · 모형 끝 면 배지 - 해석 설정 탐색 한계(모형 끝 / 비탈 끝 + 2H / 비탈 끝 + 정한 m) — [해석] 이 저장 + 그 값으로 셈 - Router 설정 저장본 · 지문 · 일꾼 kwargs · 단면 · 가정 · 확률 길 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
155 lines
5.5 KiB
TypeScript
155 lines
5.5 KiB
TypeScript
/* =============================================================================
|
|
* E03_StabSection_UI_List.ts
|
|
* E03 좌측 측점 목록(PLAN 532-3) — 측점 · 깎기/쌓기 · 조건별 최소 안전율 · 판정(아이콘 + 글) ·
|
|
* 대표 단면 별(자동 = 채움 · 사용자 켬/끔 = 밑줄 · 누르면 바꿈). 정렬 · 거름은 logic `listRows`.
|
|
* ========================================================================== */
|
|
|
|
import { createBadge, type BadgeTone } from "@ui/ui_template_badge";
|
|
import { createIconButton } from "@ui/ui_template_button";
|
|
import { el } from "@ui/ui_template_elements";
|
|
import { createEmpty } from "@ui/ui_template_empty";
|
|
import { t, type LocaleKey } from "@ui/ui_template_locale";
|
|
import { stationLabel } from "../common_util/common_util_station";
|
|
import { e03Table } from "./E03_StabSection_UI_Table";
|
|
import {
|
|
basisOf,
|
|
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;
|
|
/** 측점 간격(m) — 측점 글 「No.+거리」 의 분모 */
|
|
interval: number;
|
|
/** 사용자 켬/끔 — 서버 값(별 누름 = 바로 저장) */
|
|
representative: (row: StationRow) => { on: boolean; user: boolean | null };
|
|
editable: boolean;
|
|
}
|
|
|
|
const VERDICT_TONE: Record<string, BadgeTone> = { ok: "success", ng: "danger", none: "neutral" };
|
|
|
|
export function buildList(
|
|
draw: ListDraw,
|
|
onSelect: (key: string) => void,
|
|
onToggle: (key: string) => void,
|
|
): HTMLElement {
|
|
const box = el("div", { className: "e03-list" });
|
|
const rows = listRows(draw.rows, draw.condition, draw.sort, draw.ngOnly);
|
|
const columns = [
|
|
{ label: t("E03_Col_Station") },
|
|
{ label: t("E03_Col_Role") },
|
|
...draw.conditions.map((c) => ({ label: t(`E03_Cond_${c}` as LocaleKey), num: true })),
|
|
{ label: t("E03_Verdict") },
|
|
{ label: t("E03_Col_Basis"), title: t("E03_Basis_Hint") },
|
|
{ label: t("E03_Col_Rep"), title: t("E03_Rep_Hint") },
|
|
];
|
|
const cells = rows.map((row) => {
|
|
const v = verdictOf(row, draw.condition);
|
|
const rep = draw.representative(row);
|
|
const 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");
|
|
const star = createIconButton({
|
|
glyph: rep.on ? "★" : "☆",
|
|
title,
|
|
size: 20,
|
|
pressed: rep.on,
|
|
disabled: !draw.editable,
|
|
onClick: (ev) => {
|
|
ev.stopPropagation();
|
|
onToggle(row.key);
|
|
},
|
|
});
|
|
star.classList.add("e03-star");
|
|
star.classList.toggle("is-on", rep.on);
|
|
star.classList.toggle("is-user", rep.user !== null);
|
|
const verdict = createBadge(
|
|
row.error ? t("E03_Error") : v === "none" ? "-" : t(`E03_Verdict_${v}` as LocaleKey),
|
|
VERDICT_TONE[v],
|
|
{ kind: "lamp" },
|
|
);
|
|
verdict.classList.add("e03-verdict", `e03-verdict--${v}`);
|
|
const station = el("span", {
|
|
text: stationLabel(row.chainage_m, draw.interval, 1),
|
|
children: row.walls
|
|
? [el("span", { className: "e03-wall-tag", text: "▦", attrs: { title: t("E03_Walls") } })]
|
|
: [],
|
|
});
|
|
return [
|
|
station,
|
|
row.sides.length ? roleText(row, { cut: t("E03_CutShort"), fill: t("E03_FillShort") }) : "-",
|
|
...draw.conditions.map((c) => fmt(row.min[c])),
|
|
verdict,
|
|
basisBadges(row, draw.condition),
|
|
star,
|
|
];
|
|
});
|
|
const list = e03Table(columns, cells, "station-list");
|
|
list.table.tHead?.rows[0].cells
|
|
.item(2 + draw.conditions.indexOf(draw.condition))
|
|
?.setAttribute("aria-sort", "other");
|
|
list.rows.forEach((tr, i) => {
|
|
const row = rows[i];
|
|
tr.dataset.key = row.key;
|
|
tr.dataset.verdict = verdictOf(row, draw.condition);
|
|
tr.classList.toggle("is-active", row.key === draw.active);
|
|
if (row.error) tr.title = row.error;
|
|
tr.addEventListener("click", () => onSelect(row.key));
|
|
});
|
|
box.append(list.root);
|
|
if (!rows.length) box.append(hookEmpty(t("E03_ListEmpty")));
|
|
queueMicrotask(() => box.querySelector("tr.is-active")?.scrollIntoView({ block: "nearest" }));
|
|
return box;
|
|
}
|
|
|
|
/** 판정 면 칸 — 원호 · 비원호 + 「면 확인」 · 「모형 끝 면」(그 조건 기준) */
|
|
export function basisBadges(row: StationRow, condition: Condition): HTMLElement {
|
|
const box = el("span", { className: "e03-basis" });
|
|
const b = basisOf(row, condition);
|
|
if (b.method) {
|
|
const kind = createBadge(
|
|
t(b.circular ? "E03_Basis_Circular" : "E03_Basis_NonCircular"),
|
|
b.circular ? "neutral" : "info",
|
|
{ size: 18 },
|
|
);
|
|
kind.title = t(`E03_Method_${b.method}` as LocaleKey);
|
|
kind.dataset.role = "basis-kind";
|
|
box.append(kind);
|
|
}
|
|
if (b.surfaceCheck) {
|
|
const flag = createBadge(t("E03_SurfaceCheck"), "warning", { size: 18 });
|
|
flag.title = t("E03_SurfaceCheck_Hint");
|
|
flag.dataset.role = "basis-check";
|
|
box.append(flag);
|
|
}
|
|
if (b.edge) {
|
|
const flag = createBadge(t("E03_EdgeSurface"), "warning", { size: 18 });
|
|
flag.title = t("E03_EdgeSurface_Hint");
|
|
flag.dataset.role = "basis-edge";
|
|
box.append(flag);
|
|
}
|
|
return box;
|
|
}
|
|
|
|
function hookEmpty(text: string): HTMLElement {
|
|
const empty = createEmpty({ text, small: true });
|
|
empty.classList.add("e03-empty");
|
|
return empty;
|
|
}
|