feat(710-2): M01 단가산출 상세 보기 고르기 — 머리줄 분할 고르기 · 고른 덩이만 · 기본정보 접힘 한 줄 · 지금 입력

- 새 M01_MasterData_UI_LogicLab_View.ts — 보기 넷(단가산출 상세 기본 · 인력·재료·경비 준비 중 · 시험 계산 · 텍스트 수식) · [신규 로직] 왼쪽 세로 나눔선
- 덩이 이름표 data-block + 그릇 data-view CSS 숨김 — 시험 계산은 숨어도 그려 두어 자동 계산 돎 · 로직 바꿔도 보기 유지
- 흐름 로직(FlowView) 두 덩이도 같은 이름표
- 기본정보 = 맨 위 접힌 한 줄(키 · 원문번호 · 이름 · 결과단위) · 막힘 까닭은 접어도 보임
- 단가산출 상세 머리 「지금 입력」 한 줄(읽기만)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 15:13:23 +09:00
co-authored by Claude Opus 5.5
parent 9bcd8483af
commit a0bd269d7f
6 changed files with 153 additions and 39 deletions
@@ -37,6 +37,8 @@ import { tl, type LabTextKey } from "./M01_MasterData_UI_LogicLab_Text";
import "./M01_MasterData_UI_LogicLab_Style.css";
import "./M01_MasterData_UI_LogicLab_Pill.css";
import { createBadge } from "@ui/ui_template_badge";
import { createEmpty } from "@ui/ui_template_empty";
import { block, nowInputs } from "./M01_MasterData_UI_LogicLab_View";
/** 서버 조각(`/text` 줄의 `조각`) → 수량 칸 조각 — 요소·표·로직은 알약(글 = 조각 글) · 나머지는 글자 */
function toFragments(pieces: TextPiece[], ctx: DetailContext): Fragment[] {
@@ -142,7 +144,7 @@ const section = (title: string, body: HTMLElement[], hint = ""): HTMLElement =>
],
});
function infoBox(ctx: DetailContext): HTMLElement {
function infoBox(ctx: DetailContext): HTMLElement[] {
const row = ctx.row;
const own = isOwnLogic(row);
const field = (label: string, key: "이름" | "원문번호" | "결과단위" | "출처"): HTMLElement => {
@@ -189,32 +191,46 @@ function infoBox(ctx: DetailContext): HTMLElement {
}),
]
: [];
return section(tl("Info_Title"), [
el("p", {
className: "ui-text ui-text--muted",
text: tl(own ? "Info_Own" : ctx.admin ? "Info_Base" : "Info_ReadOnly"),
}),
...reasons,
el("div", {
className: "m01lab__info",
children: [
cell(tx("Head_File"), ctx.file),
cell(tx("Head_Key"), row.키),
field(tx("Head_Number"), "원문번호"),
field(tx("Head_Name"), "이름"),
field(tx("Head_Unit"), "결과단위"),
field(tx("Head_Source"), "출처"),
cell(tx("Head_Owner"), row.소유 ?? ""),
el("label", {
className: "m01lab__cell m01lab__cell--wide",
children: [
el("span", { className: "ui-text ui-text--muted", text: tx("Head_Note") }),
note,
],
}),
],
}),
]);
const grid = el("div", {
className: "m01lab__info",
children: [
cell(tx("Head_File"), ctx.file),
cell(tx("Head_Key"), row.키),
field(tx("Head_Number"), "원문번호"),
field(tx("Head_Name"), "이름"),
field(tx("Head_Unit"), "결과단위"),
field(tx("Head_Source"), "출처"),
cell(tx("Head_Owner"), row.소유 ?? ""),
el("label", {
className: "m01lab__cell m01lab__cell--wide",
children: [
el("span", { className: "ui-text ui-text--muted", text: tx("Head_Note") }),
note,
],
}),
],
});
// 늘 맨 위 접힌 한 줄 — 펼침은 이 로직 안에서 기억(다시 그려도) · 막힘 까닭은 접어도 보임
const head = [row.키, row.원문번호, row.이름, row.결과단위].filter(Boolean).join(" · ");
const fold = el("details", {
className: "m01lab__box m01lab__infofold",
attrs: ctx.ownHost.dataset.info ? { open: "" } : {},
children: [
el("summary", {
children: [
el("strong", { text: tl("Info_Title") }),
el("span", { className: "ui-text ui-text--muted", text: head }),
],
}),
el("p", {
className: "ui-text ui-text--muted",
text: tl(own ? "Info_Own" : ctx.admin ? "Info_Base" : "Info_ReadOnly"),
}),
grid,
],
});
fold.addEventListener("toggle", () => (ctx.ownHost.dataset.info = fold.open ? "1" : ""));
return [fold, ...reasons];
}
/** 수량 칸 = 계산 답의 고른 수량(% 덧줄은 율) · 계산 전 「—」 */
@@ -453,10 +469,13 @@ function ownEditor(ctx: DetailContext): HTMLElement {
function hoBox(ctx: DetailContext): HTMLElement {
const money = !("결과" in ctx.row) && (ctx.row.결과단위 ?? "").startsWith("원");
if (!money) {
return section(tl("Ho_Title"), [
el("p", { className: "ui-text ui-text--muted", text: tl("Not_Money") }),
el("pre", { className: "m01lab__expr", text: ctx.row.결과?.식 ?? "" }),
]);
return block(
section(tl("Ho_Title"), [
el("p", { className: "ui-text ui-text--muted", text: tl("Not_Money") }),
el("pre", { className: "m01lab__expr", text: ctx.row.결과?.식 ?? "" }),
]),
"ho",
);
}
const all = entries(ctx);
const calculated = ctx.lines !== null;
@@ -479,7 +498,7 @@ function hoBox(ctx: DetailContext): HTMLElement {
fold.addEventListener("toggle", () => (ctx.ownHost.dataset.fold = fold.open ? "1" : ""));
const blocks = isOwnLogic(ctx.row) ? [ownEditor(ctx), fold, totalLine] : [...tables, totalLine];
const hint = calculated ? tl("Click_Hint") : `${tl("Click_Hint")} · ${tl("Calc_First")}`;
return section(tl("Ho_Title"), blocks, hint);
return block(section(tl("Ho_Title"), [nowInputs(ctx.values), ...blocks], hint), "ho");
}
/** 상세 화면 넷을 `host` 에 쌓음 */
@@ -492,10 +511,14 @@ export function buildLabDetail(host: HTMLElement, ctx: DetailContext): void {
active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement
? { node: active, from: active.selectionStart, to: active.selectionEnd }
: null;
// 보기 고르기(`_View.ts`) — 덩이마다 이름표 · 고른 덩이만 보임 · 시험 계산은 숨어도 그려 둠
host.replaceChildren(
infoBox(ctx),
section(tl("Calc_Title"), [ctx.calcHost]),
...(ctx.flowHost ? [ctx.flowHost] : [hoBox(ctx), section(tl("Formula_Title"), [formula])]),
...infoBox(ctx),
block(section(tl("Calc_Title"), [ctx.calcHost]), "calc"),
block(section(tl("View_Est"), [createEmpty({ text: tl("View_Soon"), small: true })]), "est"),
...(ctx.flowHost
? [block(nowInputs(ctx.values), "ho"), ctx.flowHost]
: [hoBox(ctx), block(section(tl("Formula_Title"), [formula]), "formula")]),
);
const own = kept?.node.dataset.own; // 기본정보 칸은 새로 만들어지므로 이름표로 다시 찾음
const back = own ? host.querySelector<HTMLInputElement>(`[data-own="${own}"]`) : kept?.node;