Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into sub_laptop_2
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
import { createBanner } from "@ui/ui_template_banner";
|
||||
import { createCheckbox } from "@ui/ui_template_checkbox";
|
||||
import { createPanelCard } from "@ui/ui_template_collapsible";
|
||||
import { tableRow, tableShell, type Column } from "@ui/ui_template_table";
|
||||
import { t } from "@ui/ui_template_locale";
|
||||
import { stationLabel } from "../common_util/common_util_station";
|
||||
import { clearState, readState, writeState } from "../A00_Common/b_page_state";
|
||||
@@ -212,42 +213,40 @@ export async function renderD04PlanConfirm(root: HTMLElement): Promise<void> {
|
||||
|
||||
function draw(): void {
|
||||
const list = current();
|
||||
const head = el("tr", {
|
||||
children: [
|
||||
"D04_Col_Year",
|
||||
"D04_Col_Length",
|
||||
"D04_Col_Start",
|
||||
"D04_Col_End",
|
||||
"D04_Col_Chainage",
|
||||
"D04_Col_Confirmed",
|
||||
"D04_Col_Assignees",
|
||||
].map((key) => el("th", { text: t(key as Parameters<typeof t>[0]) })),
|
||||
});
|
||||
const rows = summary.years.map((row) => {
|
||||
const columns: Column[] = [
|
||||
"D04_Col_Year",
|
||||
"D04_Col_Length",
|
||||
"D04_Col_Start",
|
||||
"D04_Col_End",
|
||||
"D04_Col_Chainage",
|
||||
"D04_Col_Confirmed",
|
||||
"D04_Col_Assignees",
|
||||
].map((key, i) => ({
|
||||
label: t(key as Parameters<typeof t>[0]),
|
||||
...(i === 1 ? { align: "end" as const } : {}),
|
||||
}));
|
||||
const { root: tableRoot, tbody } = tableShell(columns);
|
||||
tableRoot.querySelector("table")?.classList.add("d04-confirm__table");
|
||||
for (const row of summary.years) {
|
||||
const firms = list.filter((item) => item.year === row.year).map(firmChip);
|
||||
const chain = row.saved
|
||||
? `${station(row.start_chainage_m)} ~ ${station(row.end_chainage_m)}`
|
||||
: t("D04_NotSaved");
|
||||
return el("tr", {
|
||||
attrs: { "data-year": String(row.year) },
|
||||
children: [
|
||||
el("td", { text: String(row.year) }),
|
||||
el("td", { text: fmt(row.length_m), attrs: { "data-num": "" } }),
|
||||
el("td", { text: where(row.start) }),
|
||||
el("td", { text: where(row.end) }),
|
||||
el("td", { text: chain }),
|
||||
el("td", { text: kstText(row.confirmed_at) }),
|
||||
el("td", { children: editable ? [...firms, addForm(row.year)] : firms }),
|
||||
],
|
||||
});
|
||||
});
|
||||
const table = el("table", {
|
||||
className: "d04-confirm__table",
|
||||
children: [el("thead", { children: [head] }), el("tbody", { children: rows })],
|
||||
});
|
||||
const tr = tableRow(columns, [
|
||||
String(row.year),
|
||||
fmt(row.length_m),
|
||||
where(row.start),
|
||||
where(row.end),
|
||||
chain,
|
||||
kstText(row.confirmed_at),
|
||||
el("div", { children: editable ? [...firms, addForm(row.year)] : firms }),
|
||||
]);
|
||||
tr.dataset.year = String(row.year);
|
||||
tbody.append(tr);
|
||||
}
|
||||
const notes = draft() ? [createBanner({ tone: "warning", text: t("D04_Dirty") })] : [];
|
||||
notes[0]?.classList.add("d04-confirm__dirty");
|
||||
main.replaceChildren(el("h2", { text: summary.name }), ...notes, table);
|
||||
main.replaceChildren(el("h2", { text: summary.name }), ...notes, tableRoot);
|
||||
|
||||
pickList.replaceChildren(
|
||||
...summary.years
|
||||
|
||||
@@ -10,29 +10,6 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.d04-confirm__table {
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-body);
|
||||
}
|
||||
|
||||
.d04-confirm__table th,
|
||||
.d04-confirm__table td {
|
||||
padding: var(--spacing-8);
|
||||
border: 1px solid var(--color-border);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.d04-confirm__table thead th {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.d04-confirm__table td[data-num] {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.d04-confirm__firm,
|
||||
.d04-confirm__add {
|
||||
display: flex;
|
||||
|
||||
@@ -41,38 +41,11 @@
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.e02-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-body);
|
||||
}
|
||||
|
||||
.e02-table th {
|
||||
padding: var(--spacing-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.e02-table td {
|
||||
padding: 2px var(--spacing-4);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* 표 모양은 표 부품 — 여기는 읽기 표 줄 안 줄바꿈만 */
|
||||
.e02-table--read td {
|
||||
padding: 2px var(--spacing-8);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.e02-table--read tr.is-zoned td {
|
||||
background: var(--color-mist-violet);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.e02-stations {
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
@@ -112,14 +85,6 @@
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.e02-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.e02-status {
|
||||
margin: var(--spacing-8) 0 0;
|
||||
white-space: pre-line;
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "@ui/ui_template_elements";
|
||||
import { createCard } from "@ui/ui_template_card";
|
||||
import { createCheckbox } from "@ui/ui_template_checkbox";
|
||||
import { createTable } from "@ui/ui_template_table";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { t, type LocaleKey } from "@ui/ui_template_locale";
|
||||
import { inferStationInterval, stationLabel } from "../common_util/common_util_station";
|
||||
@@ -119,16 +120,15 @@ export function headButtons(
|
||||
head.append(...buttons);
|
||||
}
|
||||
|
||||
export function table(headers: string[], rows: HTMLElement[][]): HTMLTableElement {
|
||||
const node = el("table", { className: "e02-table" }) as HTMLTableElement;
|
||||
const tr = node.createTHead().insertRow();
|
||||
for (const text of headers) tr.append(el("th", { text }));
|
||||
const body = node.createTBody();
|
||||
for (const cells of rows) {
|
||||
const row = body.insertRow();
|
||||
for (const cell of cells) row.insertCell().append(cell);
|
||||
}
|
||||
return node;
|
||||
/** 표 부품(데이터표) — 머리 글 "" = 줄 끝 단추 칸 · 돌려주는 것 = 표 감싸개(속 `table.e02-table`) */
|
||||
export function table(headers: string[], rows: HTMLElement[][]): HTMLElement {
|
||||
const wrap = createTable(
|
||||
headers.map((label) => ({ label })),
|
||||
rows,
|
||||
{ density: "data" },
|
||||
);
|
||||
wrap.querySelector("table")?.classList.add("e02-table");
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function defaultMaterial(view: E02View, id: string): Material | undefined {
|
||||
@@ -200,9 +200,9 @@ function materialsCard(view: E02View): HTMLElement {
|
||||
const mapRow = el("div", { className: "e02-map" });
|
||||
mapRow.append(el("span", { className: "e02-map__title", text: t("E02_LabelMap") }));
|
||||
for (const label of LABELS) {
|
||||
const field = el("label", { className: "e02-field" });
|
||||
const field = el("label", { className: "ui-field e02-field" });
|
||||
field.append(
|
||||
el("span", { text: L(`E02_Label_${label}`) }),
|
||||
el("span", { className: "ui-field__label", text: L(`E02_Label_${label}`) }),
|
||||
selectInput(view, `label_map.${label}`, layerOptions(g), g.label_map[label], (v) => {
|
||||
g.label_map[label] = v;
|
||||
view.rebuild();
|
||||
@@ -327,8 +327,11 @@ function zonesCard(view: E02View): { section: HTMLElement; refresh: () => void }
|
||||
el("span", { text: fmt(r.soil_thickness_m) }),
|
||||
]),
|
||||
);
|
||||
resolved.forEach((r, i) => node.tBodies[0].rows[i].classList.toggle("is-zoned", r.zoned));
|
||||
node.classList.add("e02-table--read");
|
||||
node.querySelectorAll("tbody tr").forEach((tr, i) => {
|
||||
tr.classList.toggle("is-zoned", resolved[i].zoned);
|
||||
tr.classList.toggle("is-active", resolved[i].zoned);
|
||||
});
|
||||
node.querySelector("table")?.classList.add("e02-table--read");
|
||||
stationsHost.append(node);
|
||||
};
|
||||
refresh();
|
||||
@@ -393,7 +396,7 @@ function safetyCard(view: E02View): HTMLElement {
|
||||
]);
|
||||
const { section, head } = card(
|
||||
"E02_Card_Safety",
|
||||
table(["", ...SLOPES.map((s) => L(`E02_Slope_${s}`))], rows),
|
||||
table(["\u00a0", ...SLOPES.map((s) => L(`E02_Slope_${s}`))], rows),
|
||||
);
|
||||
headButtons(
|
||||
view,
|
||||
@@ -416,8 +419,8 @@ function loadsCard(view: E02View): HTMLElement {
|
||||
const g = view.ground;
|
||||
const d = view.defaults;
|
||||
const field = (label: string, input: HTMLElement): HTMLElement => {
|
||||
const node = el("label", { className: "e02-field" });
|
||||
node.append(el("span", { text: label }), input);
|
||||
const node = el("label", { className: "ui-field e02-field" });
|
||||
node.append(el("span", { className: "ui-field__label", text: label }), input);
|
||||
return node;
|
||||
};
|
||||
const kh = numInput(view, "seismic.kh", g.seismic.kh, (v) => (g.seismic.kh = v as number), {
|
||||
|
||||
@@ -23,8 +23,8 @@ import {
|
||||
const L = (key: string): string => t(key as LocaleKey);
|
||||
|
||||
function field(label: string, input: HTMLElement): HTMLElement {
|
||||
const node = el("label", { className: "e02-field" });
|
||||
node.append(el("span", { text: label }), input);
|
||||
const node = el("label", { className: "ui-field e02-field" });
|
||||
node.append(el("span", { className: "ui-field__label", text: label }), input);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ def test_d04_parts():
|
||||
assert "input:checked" in ts # [확정] 이 고른 연도를 읽는 길
|
||||
assert "border:" not in css.split(".d04-confirm__input")[1].split("}")[0]
|
||||
assert ".d04-confirm__dirty" not in css
|
||||
assert 'el("table"' not in ts and "tableShell(columns)" in ts
|
||||
assert "d04-confirm__table" not in css # 표 모양은 표 부품 · 이름은 촬영 장면용 걸이
|
||||
|
||||
|
||||
def test_e01_missing_is_banner_part():
|
||||
@@ -77,3 +79,6 @@ def test_e02_parts():
|
||||
for dead in (".e02-card__head", ".e02-card__title", ".e02-hint", ".e02-empty"):
|
||||
assert dead not in css, dead
|
||||
_no_hex("E02_StabGround/E02_StabGround_UI_Style.css")
|
||||
assert "createTable(" in tables and "createTHead" not in tables
|
||||
assert 'className: "ui-field e02-field"' in tables # 칸 이름 줄은 입력 부품의 ui-field
|
||||
assert ".e02-field" not in css and "border-collapse" not in css
|
||||
|
||||
@@ -236,9 +236,9 @@ def test_저장된_표준단면이_브라우저까지_간다():
|
||||
# 값으로 재는 확인은 화면 실측에 있음(보조 창 b269ea34 81.77m — 단 0 → 1 에서 성토
|
||||
# 10.9027 → 10.6743㎡). 계획서에 그 수치가 남아 있다.
|
||||
def test_다단_성토부선이_면적_트림에_들어간다():
|
||||
# 다단 · 묻힌 구간 거르기는 `_Culvert_Tiers` 로 나뉘어 있어 두 파일을 함께 읽음.
|
||||
# 다단 · 묻힌 구간 거르기는 `logic/_Cross_Culvert_Tiers` 로 나뉘어 있어 두 파일을 함께 읽음.
|
||||
source = "".join(
|
||||
(PROJECT_ROOT / "B06_Section" / f"B06_Section_UI_Cross_Culvert_{name}.ts").read_text(
|
||||
(PROJECT_ROOT / "B06_Section" / "logic" / f"B06_Section_Cross_Culvert_{name}.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
for name in ("Geom", "Tiers")
|
||||
|
||||
@@ -220,7 +220,7 @@ export const KIT_PARTS: KitPart[] = [
|
||||
demo: (s) => (s === "error" ? null : row(...([["저장", "filled"], ["취소", "ghost"], ["보기", "pill"], ["삭제", "danger"], ["초기화", "danger-ghost"], ["사용법", "link"]] as const).map(([label, variant]) => markState(createButton({ label, variant, size: 28, onClick: noop }), s)))),
|
||||
sizes: (z) => createButton({ label: String(z), size: z, iconStart: "plus", onClick: noop }) },
|
||||
{ key: "icon-button", name: "아이콘 단추", group: "basic", kinds: "고스트 · 선 · 채움 · 켜짐(aria-pressed) · 글자 기호 · 여덟 단 · title 필수", file: "ui_template_button.ts",
|
||||
demo: (s) => (s === "error" ? null : row(markState(createIconButton({ icon: "settings", title: "설정" }), s), markState(createIconButton({ icon: "refresh", title: "새로 고침", variant: "outline" }), s), markState(createIconButton({ icon: "plus", title: "더하기", variant: "filled" }), s), markState(createIconButton({ icon: "eye", title: "보기", pressed: true }), s), markState(createIconButton({ glyph: "▲", title: "위로", variant: "outline" }), s))),
|
||||
demo: (s) => (s === "error" ? null : row(markState(createIconButton({ icon: "settings", title: "설정" }), s), markState(createIconButton({ icon: "refresh", title: "새로 고침", variant: "outline" }), s), markState(createIconButton({ icon: "plus", title: "추가", variant: "filled" }), s), markState(createIconButton({ icon: "eye", title: "보기", pressed: true }), s), markState(createIconButton({ glyph: "▲", title: "위로", variant: "outline" }), s))),
|
||||
sizes: (z) => createIconButton({ icon: "settings", title: `설정 ${z}`, size: z, variant: "outline" }) },
|
||||
{ key: "input", name: "입력", group: "basic", kinds: "글 · 수 · 단위 붙음 · 찾기 · 날짜 · 여러 줄 · 여덟 단", file: "ui_template_input.ts",
|
||||
demo: (s) => fieldDemo(s, () => { const f = createInputField({ placeholder: "폭", type: "number", unit: "m", size: 32 }); return { root: f.root, node: f.input, setError: f.setError }; }),
|
||||
|
||||
Reference in New Issue
Block a user