- 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>
121 lines
4.2 KiB
TypeScript
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;
|
|
}
|