feat(748-3): 텍스트 수식 바닥 노무비 · 재료비 · 경비 고정 줄(계 없음) · 비목 상자 사이 간격 = 바닥 줄 위 여유
- 새 `LogicLab_Foot` — 바닥 비목 소계 줄 한 부품(단가산출 상세 4 칸 · 텍스트 수식 3 칸 계 칸 없음) · 상자 소계는 그대로 - 비목 상자 사이 간격 = 바닥 줄 위 여유(공통 값 `--m01lab-box-gap` 12px · 두 보기) - 줄마다 붙던 옵션 값 칩 걷음은 748-2 커밋에 함께(위 옵션 칸이 대신) - 낡은 시험 고침: test_737 바닥 줄 부품 · 칸 수 · 상자 간격 대조 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData_UI_LogicLab_Foot.ts
|
||||
* 화면(모달) 바닥에 붙는 비목 소계 줄(737-1 · PLAN 748-3) — 단가산출 상세(노무비 · 재료비 · 경비 · 계 네 칸) ·
|
||||
* 텍스트 수식(노무비 · 재료비 · 경비 세 칸 · 계 칸 없음 — 중복 표기) 두 보기가 같이 씀.
|
||||
* 값 = 서버 셈 그대로(여기서 다시 셈 안 함) · 계산 전 「—」.
|
||||
* ========================================================================== */
|
||||
|
||||
import { createCalcFormulaRow } from "@ui/ui_template_calc_row";
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { formatMoney } from "./M01_MasterData_UI_Logic_Money";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
|
||||
export type FootKey = "labor" | "material" | "cost";
|
||||
const LABEL: Record<FootKey, string> = { labor: "노무비", material: "재료비", cost: "경비" };
|
||||
export const FOOT_KEYS = Object.keys(LABEL) as FootKey[];
|
||||
/** 서버 비목 이름 → 칸 */
|
||||
export const FOOT_OF: Record<string, FootKey> = {
|
||||
노무비: "labor",
|
||||
재료비: "material",
|
||||
경비: "cost",
|
||||
};
|
||||
|
||||
/** 비목 소계 줄 — `total` 을 주면 맨 끝 「계」 칸까지 네 칸(주지 않으면 세 칸) */
|
||||
export function buildFoot(
|
||||
sums: Record<FootKey, number>,
|
||||
calculated: boolean,
|
||||
total?: number,
|
||||
): HTMLElement {
|
||||
const cells = FOOT_KEYS.map((k) => {
|
||||
const out = el("span", {
|
||||
className: "ui-calc-row__out is-key",
|
||||
text: calculated ? formatMoney(sums[k]) : "—",
|
||||
});
|
||||
const row = createCalcFormulaRow(LABEL[k], null, out);
|
||||
row.dataset.group = k;
|
||||
row.dataset.subtotal = calculated ? String(sums[k]) : "";
|
||||
return row;
|
||||
});
|
||||
const foot = el("div", {
|
||||
className: "m01lab__foot",
|
||||
children: [
|
||||
el("div", { className: "m01lab__sums", children: cells }),
|
||||
...(total === undefined
|
||||
? []
|
||||
: [
|
||||
el("p", {
|
||||
className: "m01lab__total",
|
||||
attrs: { "data-sum": calculated ? String(total) : "" },
|
||||
children: [
|
||||
el("span", { text: `${tl("Total")} ` }),
|
||||
el("span", { text: calculated ? formatMoney(total) : "—" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
],
|
||||
});
|
||||
if (total === undefined) foot.dataset.cols = "3";
|
||||
return foot;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { optionGrid, varRow } from "../A00_Common/calc_sheet";
|
||||
import { formatMoney } from "./M01_MasterData_UI_Logic_Money";
|
||||
import { safeReason, tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import { buildFoot, FOOT_KEYS, FOOT_OF, type FootKey } from "./M01_MasterData_UI_LogicLab_Foot";
|
||||
import { nameTokens, tokenRow, valueTokens } from "./M01_MasterData_UI_LogicLab_FormulaTokens";
|
||||
import "./M01_MasterData_UI_LogicLab_Formula.css";
|
||||
|
||||
@@ -223,12 +224,9 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void {
|
||||
card.dataset.fxBox = g.비목;
|
||||
return card;
|
||||
});
|
||||
host.replaceChildren(
|
||||
...boxes,
|
||||
el("p", {
|
||||
className: "m01lab__total",
|
||||
attrs: { "data-text-sum": String(text.계) },
|
||||
text: `${tl("Total")} ${formatMoney(text.계)}`,
|
||||
}),
|
||||
);
|
||||
// 748-3 맨 아래 비목 소계 고정 줄(노무비 · 재료비 · 경비 — 계 칸 없음 · 상자 소계는 그대로)
|
||||
const sums = Object.fromEntries(
|
||||
FOOT_KEYS.map((k) => [k, text.groups.find((g) => FOOT_OF[g.비목] === k)?.소계 ?? 0]),
|
||||
) as Record<FootKey, number>;
|
||||
host.replaceChildren(...boxes, buildFoot(sums, calculated));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import { createBadge } from "@ui/ui_template_badge";
|
||||
import { createCalcFormulaRow } from "@ui/ui_template_calc_row";
|
||||
import { createCard } from "@ui/ui_template_card";
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
@@ -44,6 +43,7 @@ import {
|
||||
openTableModal,
|
||||
type PickCond,
|
||||
} from "./M01_MasterData_UI_LogicLab_Modal";
|
||||
import { buildFoot, FOOT_KEYS, type FootKey } from "./M01_MasterData_UI_LogicLab_Foot";
|
||||
import { fragmentRow, type Fragment } from "./M01_MasterData_UI_LogicLab_Pill";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
|
||||
@@ -653,35 +653,17 @@ export function buildHo(ctx: HoContext): HTMLElement[] {
|
||||
const boxes = boxesOf(all, ["인력", "재료", "경비"]);
|
||||
const used = new Set(boxes.flatMap(usesOf));
|
||||
const common = (ctx.row.입력 ?? []).map((s) => s.이름).filter((n) => !used.has(n));
|
||||
const sums = (["labor", "material", "cost"] as Group[]).map((g) => {
|
||||
const v = all.filter((e) => e.group === g).reduce((s, e) => s + (e.amount ?? 0), 0);
|
||||
const out = el("span", {
|
||||
className: "ui-calc-row__out is-key",
|
||||
text: calculated ? formatMoney(v) : "—",
|
||||
});
|
||||
const row = createCalcFormulaRow(COST_OF[g], null, out);
|
||||
row.dataset.group = g;
|
||||
row.dataset.subtotal = calculated ? String(v) : "";
|
||||
return row;
|
||||
});
|
||||
const sums = Object.fromEntries(
|
||||
FOOT_KEYS.map((g) => [
|
||||
g,
|
||||
all.filter((e) => e.group === g).reduce((s, e) => s + (e.amount ?? 0), 0),
|
||||
]),
|
||||
) as Record<FootKey, number>;
|
||||
const total = all.reduce((s, e) => s + (e.amount ?? 0), 0);
|
||||
return [
|
||||
...(boxes.length || !common.length ? [] : [commonCard(optionRows(common, ctx))]),
|
||||
...boxes.map((b) => boxCard(b, ctx, calculated, common)),
|
||||
// 737-1 맨 아래 한 줄 4 칸(노무비 · 재료비 · 경비 · 계) — 화면(모달) 바닥에 붙음
|
||||
el("div", {
|
||||
className: "m01lab__foot",
|
||||
children: [
|
||||
el("div", { className: "m01lab__sums", children: sums }),
|
||||
el("p", {
|
||||
className: "m01lab__total",
|
||||
attrs: { "data-sum": calculated ? String(total) : "" },
|
||||
children: [
|
||||
el("span", { text: `${tl("Total")} ` }),
|
||||
el("span", { text: calculated ? formatMoney(total) : "—" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
buildFoot(sums, calculated, total),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: var(--spacing-8);
|
||||
margin-top: var(--spacing-12);
|
||||
margin-top: var(--m01lab-box-gap, var(--spacing-12));
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
@@ -272,6 +272,20 @@
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* 748-3 텍스트 수식 바닥 줄 = 세 칸(계 칸 없음) */
|
||||
.m01lab__foot[data-cols="3"] {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
/* 748-3 비목 상자 사이 간격 = 바닥 고정 줄 위 여유(같은 값 · 두 보기) */
|
||||
.m01-logic__editor {
|
||||
--m01lab-box-gap: var(--spacing-12);
|
||||
}
|
||||
|
||||
.m01lab__ho-box + .m01lab__ho-box {
|
||||
margin-top: var(--m01lab-box-gap, var(--spacing-12));
|
||||
}
|
||||
|
||||
.m01lab__foot .ui-calc-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -27,7 +27,10 @@ def test_합계_한_줄_바닥_고정():
|
||||
assert "position: sticky" in foot and "bottom: 0" in foot
|
||||
assert "repeat(4, minmax(0, 1fr))" in foot
|
||||
assert "overflow: clip" in STYLE # 메인 칸이 굴림 칸이면 바닥에 안 붙음
|
||||
assert 'className: "m01lab__foot"' in HO
|
||||
foot_ts = (M01 / "M01_MasterData_UI_LogicLab_Foot.ts").read_text(encoding="utf-8")
|
||||
assert 'className: "m01lab__foot"' in foot_ts # 748-3 두 보기가 같은 바닥 줄 부품
|
||||
assert "buildFoot(sums, calculated, total)" in HO and "buildFoot(sums, calculated)" in FX
|
||||
assert 'data-cols="3"' in STYLE and ".m01lab__ho-box + .m01lab__ho-box" in STYLE
|
||||
|
||||
|
||||
def test_공통_옵션은_모든_박스에_같이():
|
||||
|
||||
Reference in New Issue
Block a user