Files
Aislo/E03_StabSection/E03_StabSection_UI_List.ts
T
eomsangdonandClaude Opus 5.5 164024ab43 feat(532-5): E03 2D 해석에 벽(옹벽 · 석축) 연결 · 횡단 없는 측점 띠(E02 · E03)
- analyze2d 가 측점마다 벽(walls_by_station · 복사 지문으로 기억) · 그려지는 설계선을 넘김 · 벽 있는 측점만 지문에 벽 · 벽 없는 측점 = 종전 지문
- 측점 보기 = 같은 벽 모형 · 수위선 = E02 고른 지하수 갈래(station_groundwater)
- 화면 — 「반영 전」 안내 걷음 · 벽 회색 빗금 · 결과 「벽 있음」 · 목록 벽 표시
- result2d · stab/ground 에 missing_stations 칸 덧붙임 · 화면 위 띠(펼침 목록)
- E03 지반 조건 = E02 화면과 같은 upgrade(옛 정본 지하수 칸 기본값)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:19:59 +09:00

121 lines
4.2 KiB
TypeScript

/* =============================================================================
* 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;
}