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:
@@ -52,6 +52,7 @@ import {
|
||||
} from "./M01_MasterData_UI_LogicLab_Flow";
|
||||
import { askBasicInfo } from "./M01_MasterData_UI_LogicLab_Basic";
|
||||
import { buildLogicSide } from "./M01_MasterData_UI_LogicLab_Side";
|
||||
import { createViewPicker } from "./M01_MasterData_UI_LogicLab_View";
|
||||
import { buildHeadBar } from "./M01_MasterData_UI_Head";
|
||||
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
||||
import type { CfLogic } from "../M02_MasterTemplete/DataFlow/M02_DF_Model";
|
||||
@@ -130,6 +131,7 @@ export async function mountM01LogicLab(
|
||||
const editor = el("div", { className: "m01-logic__editor" });
|
||||
// 로직 상세 — 왼쪽 지금 내용 · 오른쪽 접는 패널 둘(흐름도 · 원문 · 하나만 열림 · 폭 공유)
|
||||
const panes = buildLogicSide(editor);
|
||||
const views = createViewPicker(editor); // 보기 고르기 — 머리줄 [신규 로직] 왼쪽
|
||||
const errors = el("div", {
|
||||
className: "ui-banner ui-banner--danger m01-logic__reasons",
|
||||
attrs: { hidden: "" },
|
||||
@@ -236,7 +238,7 @@ export async function mountM01LogicLab(
|
||||
list.root.hidden = !listing || !loaded;
|
||||
waiting.hidden = loaded;
|
||||
panes.root.hidden = !opened;
|
||||
back.hidden = listing;
|
||||
back.hidden = views.root.hidden = listing;
|
||||
if (!opened) return;
|
||||
if (!opened.row) {
|
||||
editor.replaceChildren(createEmpty({ text: `● ${tx("List_Deleted")}`, small: true }));
|
||||
@@ -582,6 +584,7 @@ export async function mountM01LogicLab(
|
||||
title: tx("Title"),
|
||||
status: [pending],
|
||||
buttons: [
|
||||
views.root,
|
||||
createButton({ label: tf("New"), variant: "filled", onClick: () => void onNew() }),
|
||||
copyButton,
|
||||
createButton({ label: tx("Bar_Delete"), variant: "danger", onClick: () => void onDelete() }),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -159,9 +159,11 @@ export function buildFlowView(
|
||||
try {
|
||||
ev = opts.result ?? evalLogic(lg, values, { logics: opts.logics });
|
||||
} catch (e) {
|
||||
host.replaceChildren(
|
||||
section(tf("Detail"), [el("p", { className: "ui-text ui-text--muted", text: String(e) })]),
|
||||
);
|
||||
const failed = section(tf("Detail"), [
|
||||
el("p", { className: "ui-text ui-text--muted", text: String(e) }),
|
||||
]);
|
||||
failed.dataset.block = "ho";
|
||||
host.replaceChildren(failed);
|
||||
return;
|
||||
}
|
||||
const totalRound = totalRoundOf(lg, totalRoundNow());
|
||||
@@ -196,5 +198,8 @@ export function buildFlowView(
|
||||
tf("Formula"),
|
||||
bodies.length ? bodies : [el("p", { className: "ui-text ui-text--muted", text: tf("NoFunc") })],
|
||||
);
|
||||
// 보기 고르기 이름표(`LogicLab_View.ts`) — 고른 덩이만 보임
|
||||
detail.dataset.block = "ho";
|
||||
text.dataset.block = "formula";
|
||||
host.replaceChildren(detail, text);
|
||||
}
|
||||
|
||||
@@ -190,3 +190,28 @@
|
||||
.m01lab__pickrow.is-picked {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 보기 고르기(710-2 · `_View.ts`) — 그릇 data-view 와 다른 이름표 덩이는 숨김(시험 계산은 숨어도 돎) */
|
||||
.m01-logic__editor[data-view="ho"] [data-block]:not([data-block="ho"]),
|
||||
.m01-logic__editor[data-view="est"] [data-block]:not([data-block="est"]),
|
||||
.m01-logic__editor[data-view="calc"] [data-block]:not([data-block="calc"]),
|
||||
.m01-logic__editor[data-view="formula"] [data-block]:not([data-block="formula"]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 기본정보 — 늘 맨 위 접힌 한 줄 */
|
||||
.m01lab__infofold > summary {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--spacing-8);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m01lab__infofold[open] > summary {
|
||||
margin-bottom: var(--spacing-8);
|
||||
}
|
||||
|
||||
/* 상세 머리 「지금 입력」 한 줄 */
|
||||
.m01lab__now {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,14 @@ const TEXT = {
|
||||
Formula_Title: ["텍스트 수식", "Text formula"],
|
||||
Formula_Slot: ["시험 계산을 누르면 식이 글로 나옴", "Run the trial calc to see the formula"],
|
||||
Calc_Title: ["시험 계산", "Trial calculation"],
|
||||
View_Est: ["인력·재료·경비", "Labor·material·expense"],
|
||||
View_Label: ["보기", "View"],
|
||||
View_Soon: [
|
||||
"준비 중 — 인력 · 재료 · 경비 묶음 보기",
|
||||
"Coming soon — labor · material · expense view",
|
||||
],
|
||||
Now_Inputs: ["지금 입력", "Current inputs"],
|
||||
Now_None: ["없음", "none"],
|
||||
G_labor: ["인력", "Labor"],
|
||||
G_material: ["재료", "Material"],
|
||||
G_cost: ["경비", "Expense"],
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData_UI_LogicLab_View.ts
|
||||
* 「단가산출 로직」 상세 보기 고르기 — 머리줄 [신규 로직] 왼쪽 「분할 고르기 | 세로 나눔선」
|
||||
* 보기 넷 = 단가산출 상세(기본) · 인력·재료·경비 · 시험 계산 · 텍스트 수식 — 고른 덩이만 보임
|
||||
* 덩이 = `data-block` 이름표 · 숨김 = 그릇 `data-view` + CSS(`LogicLab_Style.css`) — 다시 그려도 고름 유지
|
||||
* 시험 계산은 숨겨도 그려 둠(자동 계산이 숨은 칸에서도 돎) · 기본정보는 이름표 없음(늘 맨 위)
|
||||
* 기억 = 이 화면 안에서만(로직을 바꿔도 유지 · 새로 들어오면 단가산출 상세)
|
||||
* ========================================================================== */
|
||||
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { createSegment } from "@ui/ui_template_segment";
|
||||
import { createToolbar } from "@ui/ui_template_toolbar";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
|
||||
export type LabView = "ho" | "est" | "calc" | "formula";
|
||||
|
||||
/** 덩이에 보기 이름표를 붙여 돌려줌 */
|
||||
export const block = <T extends HTMLElement>(node: T, view: LabView): T => {
|
||||
node.dataset.block = view;
|
||||
return node;
|
||||
};
|
||||
|
||||
/** 보기 고르기 — `host`(상세 그릇)에 `data-view` 를 둠 · `root` 는 머리줄에 놓음 */
|
||||
export function createViewPicker(host: HTMLElement): { root: HTMLElement } {
|
||||
host.dataset.view = "ho";
|
||||
const segment = createSegment({
|
||||
options: [
|
||||
{ value: "ho", label: tl("Ho_Title") },
|
||||
{ value: "est", label: tl("View_Est") },
|
||||
{ value: "calc", label: tl("Calc_Title") },
|
||||
{ value: "formula", label: tl("Formula_Title") },
|
||||
],
|
||||
value: "ho",
|
||||
size: 32,
|
||||
ariaLabel: tl("View_Label"),
|
||||
onChange: (v) => (host.dataset.view = v),
|
||||
});
|
||||
return { root: createToolbar({ items: [segment.root, "divider"], label: tl("View_Label") }) };
|
||||
}
|
||||
|
||||
/** 상세 머리 「지금 입력」 한 줄(읽기만) — 숨은 시험 계산 칸에 든 값 */
|
||||
export function nowInputs(values: Record<string, string>): HTMLElement {
|
||||
const filled = Object.entries(values).filter(([, v]) => String(v ?? "").trim() !== "");
|
||||
const text = filled.length ? filled.map(([k, v]) => `${k} ${v}`).join(" · ") : tl("Now_None");
|
||||
return el("p", {
|
||||
className: "ui-text ui-text--muted m01lab__now",
|
||||
attrs: { "data-now": "" },
|
||||
text: `${tl("Now_Inputs")} · ${text}`,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user