fix(M02): 일위대가 표 이름 한 줄 말줄임 · 칸 선 이어짐(45-7)
- 이름 칸 flex 뺌(칸 선 끊김 원인) · 코드 · 이름 · 설명 글마다 한 줄 말줄임 + title - 머리 칸 · 코드 · 규격 줄바꿈 없음 · 전체 합계 칸 테두리를 안쪽 그림자로(칸 선 굵기 그대로) - 확인 시나리오 resources/tester/ui_checks/45-7_일위대가_표_줄.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -21,13 +21,25 @@
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
/* 이름 칸(45-7) — 칸은 표 칸 그대로(flex 면 칸 선이 끊김) · 코드 · 이름 · 설명은 글마다 한 줄 말줄임(전체 = title) */
|
||||
.m02-labrow__name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 12rem;
|
||||
}
|
||||
|
||||
.m02-labrow__name > * {
|
||||
display: block;
|
||||
max-width: 20rem;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.m02-labrow th,
|
||||
.m02-labrow__code,
|
||||
.m02-labrow__spec {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.m02-labrow__key,
|
||||
.m02-labrow__note,
|
||||
.m02-labrow__muted {
|
||||
@@ -129,9 +141,9 @@
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
/* 총합계 줄 전체 합계 칸(44-6) — 테두리 · 색 */
|
||||
/* 총합계 줄 전체 합계 칸(44-6) — 테두리 · 색 · 테두리는 안쪽 그림자(칸 선 굵기 안 바꿈 · 45-7) */
|
||||
.m02-labrow__sum .m02-labrow__total {
|
||||
border: 2px solid var(--color-primary);
|
||||
box-shadow: inset 0 0 0 2px var(--color-primary);
|
||||
background: var(--color-mist-violet);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
@@ -232,6 +244,10 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.m02-labrow .m02-labrow__name > * {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.m02-labrow .m02-labrow__no,
|
||||
.m02-labrow .m02-labrow__unit:not([data-label]),
|
||||
.m02-labrow .m02-labrow__code,
|
||||
|
||||
@@ -135,6 +135,12 @@ function moneyCell(
|
||||
});
|
||||
}
|
||||
|
||||
/** 이름 칸 글마다 title = 전체 글(한 줄 말줄임 · 45-7) */
|
||||
const titled = (td: HTMLElement): HTMLElement => {
|
||||
for (const c of td.children) c.setAttribute("title", c.textContent ?? "");
|
||||
return td;
|
||||
};
|
||||
|
||||
const table = (heads: string[], rows: HTMLElement[], foot: HTMLElement[]): HTMLElement =>
|
||||
el("div", {
|
||||
className: "ui-general-block__table-wrap",
|
||||
@@ -240,30 +246,32 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
attrs: { "data-labor-logic": row.로직 },
|
||||
children: [
|
||||
el("td", { className: "m02-labrow__no", text: String(i + 1) }),
|
||||
el("td", {
|
||||
className: "m02-labrow__name",
|
||||
children: [
|
||||
el("span", { className: "m02-labrow__key", text: row.로직 }),
|
||||
el("strong", { text: logic?.이름 ?? row.로직 }),
|
||||
...(values.empty
|
||||
? [
|
||||
el("small", {
|
||||
className: "m02-labrow__empty",
|
||||
text: tc("Labor_Empty").replace("{n}", String(values.empty)),
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(row.메모 ? [el("small", { className: "m02-labrow__note", text: row.메모 })] : []),
|
||||
...(why
|
||||
? [
|
||||
el("small", {
|
||||
className: "m02-labrow__empty",
|
||||
text: `${tc("Labor_Calc_Fail")}: ${why}`,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
titled(
|
||||
el("td", {
|
||||
className: "m02-labrow__name",
|
||||
children: [
|
||||
el("span", { className: "m02-labrow__key", text: row.로직 }),
|
||||
el("strong", { text: logic?.이름 ?? row.로직 }),
|
||||
...(values.empty
|
||||
? [
|
||||
el("small", {
|
||||
className: "m02-labrow__empty",
|
||||
text: tc("Labor_Empty").replace("{n}", String(values.empty)),
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(row.메모 ? [el("small", { className: "m02-labrow__note", text: row.메모 })] : []),
|
||||
...(why
|
||||
? [
|
||||
el("small", {
|
||||
className: "m02-labrow__empty",
|
||||
text: `${tc("Labor_Calc_Fail")}: ${why}`,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
),
|
||||
aliasCell(row.별칭, on?.alias ? (v) => on.alias?.(i, v) : null),
|
||||
qtyCell(line, data.cells, on?.qty ? () => on.qty?.(i) : null),
|
||||
el("td", { className: "m02-labrow__unit", text: unit }),
|
||||
@@ -382,16 +390,22 @@ export function matTable(data: MatTableData, on?: Omit<RowHandlers, "move">): HT
|
||||
attrs: { "data-labor-mat": mat.코드 },
|
||||
children: [
|
||||
el("td", { className: "m02-labrow__code", text: mat.코드 }),
|
||||
titled(
|
||||
el("td", {
|
||||
className: "m02-labrow__name",
|
||||
children: [
|
||||
el("strong", { text: mat.이름 ?? "" }),
|
||||
...(line?.까닭
|
||||
? [el("small", { className: "m02-labrow__empty", text: line.까닭 })]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
),
|
||||
el("td", {
|
||||
className: "m02-labrow__name",
|
||||
children: [
|
||||
el("strong", { text: mat.이름 ?? "" }),
|
||||
...(line?.까닭
|
||||
? [el("small", { className: "m02-labrow__empty", text: line.까닭 })]
|
||||
: []),
|
||||
],
|
||||
className: "m02-labrow__spec",
|
||||
text: mat.규격 ?? "",
|
||||
attrs: { title: mat.규격 ?? "" },
|
||||
}),
|
||||
el("td", { className: "m02-labrow__spec", text: mat.규격 ?? "" }),
|
||||
qtyCell(qtyLine, data.cells, on?.qty ? () => on.qty?.(i) : null),
|
||||
el("td", {
|
||||
className: "m02-labrow__unit",
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/* 45-7 — M02 구조물 조합 · 일위대가 표(단가산출 로직 · 자재목록) 이름 한 줄 · 칸 선 이어짐 — 카드 · 상세 두 곳
|
||||
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자 ·
|
||||
* 나무에 item1(구조물 조합 · 일위대가 로직 담김)이 있음
|
||||
* 부르는 법: orca eval --expression "$(tr '\n' ' ' < 이 파일)" (async 식 · JSON 글을 돌려줌 · 줄바꿈 있으면 orca 가 끊김)
|
||||
* 하는 일: item1 카드 표를 잼 → 일위대가 카드 [수정]으로 상세를 열어 잼 → item1 을 다시 눌러 카드 화면으로 돌아감(값 안 바꿈)
|
||||
* 기대 수치(카드 · 상세 각각):
|
||||
* - nameLines = 1(이름 칸 · 코드 · 규격 칸 글마다 줄 수 최대) · headLines = 1(머리 칸 글 줄 수 최대)
|
||||
* - spanBad = 0(각 줄 칸 수 = 머리 칸 수 · colspan 셈)
|
||||
* - broken = 0(칸 위·아래가 줄 위·아래와 어긋남 · 한 줄 안 아래 테두리 굵기가 다름 · 칸 사이 틈)
|
||||
* - ellipsis = 이름 글에 말줄임 꼴(text-overflow ellipsis) · titled = 이름 글마다 title
|
||||
* - 카드는 가로 스크롤 그대로(cardScroll = true · 표 폭 > 감싼 칸 폭)
|
||||
*/
|
||||
(async () => {
|
||||
const w = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const until = async (fn, ms = 8000) => {
|
||||
for (let t = 0; t < ms; t += 150) {
|
||||
const v = fn();
|
||||
if (v) return v;
|
||||
await w(150);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const lines = (e) => {
|
||||
const r = document.createRange();
|
||||
r.selectNodeContents(e);
|
||||
const tops = [...r.getClientRects()].filter((q) => q.width > 0).map((q) => Math.round(q.top));
|
||||
return new Set(tops).size;
|
||||
};
|
||||
const span = (tr) => [...tr.cells].reduce((a, c) => a + (c.colSpan || 1), 0);
|
||||
const measure = (t) => {
|
||||
const head = span(t.tHead.rows[0]);
|
||||
const body = [...t.querySelectorAll("tbody tr, tfoot tr")];
|
||||
const texts = [
|
||||
...t.querySelectorAll(
|
||||
".m02-labrow__name > *, tbody .m02-labrow__code, tbody .m02-labrow__spec",
|
||||
),
|
||||
...t.querySelectorAll(".m02-labrow__name:not(:has(*))"),
|
||||
].filter((e) => e.textContent.trim());
|
||||
const kids = [...t.querySelectorAll(".m02-labrow__name > *")];
|
||||
let broken = 0;
|
||||
const why = [];
|
||||
for (const tr of body) {
|
||||
const R = tr.getBoundingClientRect();
|
||||
const cells = [...tr.cells].filter((c) => c.getClientRects().length);
|
||||
const widths = new Set(cells.map((c) => getComputedStyle(c).borderBottomWidth));
|
||||
if (widths.size > 1) (broken++, why.push(`굵기 ${tr.className} ${[...widths]}`));
|
||||
cells.forEach((c, i) => {
|
||||
const r = c.getBoundingClientRect();
|
||||
if (Math.abs(r.top - R.top) > 1 || Math.abs(r.bottom - R.bottom) > 1)
|
||||
(broken++,
|
||||
why.push(`높이 ${tr.className} ${c.className} ${Math.round(r.bottom - R.bottom)}`));
|
||||
const n = cells[i + 1];
|
||||
if (n && Math.abs(n.getBoundingClientRect().left - r.right) > 1)
|
||||
(broken++, why.push(`틈 ${tr.className} ${c.className}`));
|
||||
});
|
||||
}
|
||||
const wrap = t.parentElement;
|
||||
return {
|
||||
head,
|
||||
rows: body.length,
|
||||
spanBad: body.filter((tr) => span(tr) !== head).length,
|
||||
nameLines: Math.max(0, ...texts.map(lines)),
|
||||
headLines: Math.max(0, ...[...t.tHead.rows[0].cells].map(lines)),
|
||||
broken,
|
||||
why: why.slice(0, 6),
|
||||
ellipsis: kids.every((e) => getComputedStyle(e).textOverflow === "ellipsis"),
|
||||
titled: kids.every((e) => e.title === e.textContent),
|
||||
scroll: Math.round(t.getBoundingClientRect().width) > Math.round(wrap.clientWidth),
|
||||
};
|
||||
};
|
||||
const node = await until(() => document.querySelector('[data-node="i|item1"]'));
|
||||
if (!node) return JSON.stringify({ error: "item1 없음 — 기슭막이 폴더를 펼쳐 둘 것" });
|
||||
node.click();
|
||||
const card = await until(() =>
|
||||
[...document.querySelectorAll(".m02-struct__card")].find((c) =>
|
||||
c.querySelector("table.m02-labrow"),
|
||||
),
|
||||
);
|
||||
if (!card) return JSON.stringify({ error: "일위대가 카드 표 없음" });
|
||||
await w(1500);
|
||||
const out = { card: [...card.querySelectorAll("table.m02-labrow")].map(measure) };
|
||||
out.cardScroll = out.card[0]?.scroll ?? null;
|
||||
const edit = [...card.querySelectorAll(".m02-struct__card-tools button")].find(
|
||||
(b) => b.innerText.trim() === "수정",
|
||||
);
|
||||
if (!edit) return JSON.stringify({ ...out, error: "일위대가 [수정] 없음" });
|
||||
edit.click();
|
||||
await until(() =>
|
||||
[...document.querySelectorAll("table.m02-labrow")].find((t) => !t.closest(".m02-struct__card")),
|
||||
);
|
||||
await w(2000);
|
||||
out.detail = [...document.querySelectorAll("table.m02-labrow")]
|
||||
.filter((t) => !t.closest(".m02-struct__card"))
|
||||
.map(measure);
|
||||
/* 카드 화면으로 돌아감 */
|
||||
document.querySelector('[data-node="i|item1"]')?.click();
|
||||
await w(1000);
|
||||
out.back = !!document.querySelector(".m02-struct__card table.m02-labrow");
|
||||
return JSON.stringify(out);
|
||||
})();
|
||||
Reference in New Issue
Block a user