feat(M01): 몫 줄 단가 = 단가 x 몫 비율 · 줄 모달 윗줄 요소조각 · 로직 알약이 그 로직을 엶 (PLAN 5장)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
@@ -34,6 +34,7 @@ import { buildList, logicId, type ListItem, type ListMark } from "./M01_MasterDa
|
||||
import type { SideHandle } from "./M01_MasterData_UI_Side";
|
||||
import { tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
import { buildLabDetail, isOwnLogic } from "./M01_MasterData_UI_LogicLab_Detail";
|
||||
import { setLogicOpener } from "./M01_MasterData_UI_LogicLab_Modal";
|
||||
import { openCopyScreen } from "./M01_MasterData_UI_LogicLab_Copy";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import { buildNewLogic } from "./M01_MasterData_UI_LogicLab_New";
|
||||
@@ -441,5 +442,6 @@ export async function mountM01LogicLab(
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
setLogicOpener((key) => void openKeyOn(key));
|
||||
return { openKey: openKeyOn };
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
openMaterialModal,
|
||||
openNoteModal,
|
||||
openRefModal,
|
||||
openLogic,
|
||||
openTableModal,
|
||||
type PickCond,
|
||||
} from "./M01_MasterData_UI_LogicLab_Modal";
|
||||
@@ -47,7 +48,7 @@ function toFragments(pieces: TextPiece[], ctx: DetailContext): Fragment[] {
|
||||
openTableModal({ title: piece.글, table: ref.키, row: ref.행, col: ref.열 });
|
||||
else if (piece.kind === "요소")
|
||||
openRefModal({ title: piece.글, ref: ref.키, prices: ctx.prices });
|
||||
else openNoteModal(piece.글, `${tl("Modal_Logic")} · ${ref.키}`);
|
||||
else openLogic(ref.키);
|
||||
};
|
||||
return { pill: { label: piece.글, onClick: open }, ...dim };
|
||||
});
|
||||
@@ -93,7 +94,10 @@ interface Entry {
|
||||
amount: number | null;
|
||||
/** 덧줄이면 true */
|
||||
extra: boolean;
|
||||
open: () => void;
|
||||
open: (self: Entry) => void;
|
||||
/** 서버 조각으로 그린 단가 자리 · 수량 풀이 — 줄 모달이 씀(붙기 전엔 없음) */
|
||||
unitFrag?: Fragment[];
|
||||
piecesFrag?: Fragment[];
|
||||
}
|
||||
|
||||
/** 재료 고르기 줄 — `요소` 가 글 대신 {구분·상세구분·규격·대표} 객체 */
|
||||
@@ -194,7 +198,15 @@ const spread = (e: Entry, line?: CalcLine): Entry[] => {
|
||||
...e,
|
||||
group: OF_COST[k] ?? "other",
|
||||
amount: v,
|
||||
...(parts.length > 1 ? { share: k } : {}),
|
||||
...(parts.length > 1
|
||||
? {
|
||||
share: k,
|
||||
// 몫 줄 단가 = 단가 × (몫 금액 ÷ 줄 금액) — 곱하기 수량이 몫 금액과 맞게
|
||||
...(line && line.금액 && line.단가 != null
|
||||
? { price: formatMoney((line.단가 * v) / line.금액) }
|
||||
: {}),
|
||||
}
|
||||
: {}),
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -217,8 +229,10 @@ function entries(ctx: DetailContext): Entry[] {
|
||||
price: price === undefined || price === null ? "" : formatMoney(price),
|
||||
amount: line ? line.금액 : null,
|
||||
extra: false,
|
||||
open: () =>
|
||||
open: (self) =>
|
||||
openMaterialModal({
|
||||
unitFrag: self.unitFrag,
|
||||
qtyFrag: self.piecesFrag,
|
||||
title: item.이름 ?? (cond ? condLabel(cond) : item.요소),
|
||||
expr: item.수량,
|
||||
...(cond ? { cond, unit: item.단위 } : { element: item.요소 }),
|
||||
@@ -242,8 +256,10 @@ function entries(ctx: DetailContext): Entry[] {
|
||||
price: "",
|
||||
amount: line ? line.금액 : null,
|
||||
extra: true,
|
||||
open: () =>
|
||||
open: (self) =>
|
||||
openMaterialModal({
|
||||
unitFrag: self.unitFrag,
|
||||
qtyFrag: self.piecesFrag,
|
||||
title: extra.이름,
|
||||
expr: extra.식,
|
||||
prices: ctx.prices,
|
||||
@@ -267,7 +283,9 @@ function attachPieces(all: Entry[], ctx: DetailContext): void {
|
||||
if (lines.length !== mine.length) continue;
|
||||
mine.forEach((e, i) => {
|
||||
const pieces = lines[i].조각;
|
||||
if (pieces?.length) e.qtyFrag = toFragments(pieces, ctx);
|
||||
if (pieces?.length) e.qtyFrag = e.piecesFrag = toFragments(pieces, ctx);
|
||||
const unit = lines[i].요소조각;
|
||||
if (unit?.length) e.unitFrag = toFragments(unit, ctx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -300,8 +318,8 @@ function lineRow(e: Entry): HTMLElement {
|
||||
cellOf(e.amount === null ? "" : formatMoney(e.amount), "m01-logic__money"),
|
||||
],
|
||||
});
|
||||
tr.addEventListener("click", e.open);
|
||||
tr.addEventListener("keydown", (ev) => ev.key === "Enter" && e.open());
|
||||
tr.addEventListener("click", () => e.open(e));
|
||||
tr.addEventListener("keydown", (ev) => ev.key === "Enter" && e.open(e));
|
||||
return tr;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,19 @@ import { formatNumber } from "./M01_MasterData_UI_Logic_Edit";
|
||||
import { formatMoney } from "./M01_MasterData_UI_Logic_Money";
|
||||
import { explain, loadTable, type TableRow } from "./M01_MasterData_UI_Logic_Note";
|
||||
import { jumpToMaster, type MasterJump } from "./M01_MasterData_UI_Side";
|
||||
import { fragmentRow, type Fragment } from "./M01_MasterData_UI_LogicLab_Pill";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
|
||||
/** 로직 알약 → 그 로직 열기 — 로직 화면이 자기 열기를 등록 · 열려 있는 모달은 다 닫음 */
|
||||
let logicOpener: (key: string) => void = () => {};
|
||||
export const setLogicOpener = (fn: (key: string) => void): void => {
|
||||
logicOpener = fn;
|
||||
};
|
||||
export function openLogic(key: string): void {
|
||||
document.querySelectorAll(".m01-logic__backdrop").forEach((b) => b.remove());
|
||||
logicOpener(key);
|
||||
}
|
||||
|
||||
/** 재료 고르기 조건(호표 줄 `요소` 가 글 대신 객체일 때) — 서버 `master_material.PICK_KEYS` 와 같은 칸 */
|
||||
export interface PickCond {
|
||||
구분?: string;
|
||||
@@ -51,6 +62,10 @@ export interface ModalTarget {
|
||||
middles: NamedFormula[];
|
||||
/** 시험 계산 칸에 넣은 값 — 걸린 줄을 맞히는 데 씀 */
|
||||
values: Record<string, string>;
|
||||
/** 서버 `요소조각` — 단가 자리(요소 · 로직 부르기)를 조각으로 · 없으면 로직 키만 알약으로 */
|
||||
unitFrag?: Fragment[];
|
||||
/** 서버 수량 조각 — 한 줄 풀이를 조각으로(알약 포함) · 없으면 식 풀이 글 */
|
||||
qtyFrag?: Fragment[];
|
||||
}
|
||||
|
||||
/** 수량 칸 표 알약 하나가 여는 자료 — 서버 조각 `참조`(키 · 행 · 열)가 곧 찾은 자리 */
|
||||
@@ -289,18 +304,31 @@ export function openMaterialModal(target: ModalTarget): void {
|
||||
? (target.prices[element] ?? target.prices[base])
|
||||
: undefined;
|
||||
const priceRef = cond ? pickedOf(cond, target.values, known?.ref ?? "") : element;
|
||||
const logicRef = element.startsWith("로직(") ? element : "";
|
||||
const logicKey = /^로직\((\w+)/.exec(element)?.[1] ?? "";
|
||||
const sum = el("p", { className: "m01lab__sum" });
|
||||
const setSum = (price: ElementBrief | null | undefined): void => {
|
||||
const unit = price ? `${tl("Modal_UnitPrice")} ${formatMoney(price.값)} × ` : "";
|
||||
sum.textContent = `${target.title} = ${unit}${explain(target.expr)}`;
|
||||
if (target.qtyFrag) {
|
||||
sum.replaceChildren(`${target.title} = ${unit}`, fragmentRow(target.qtyFrag));
|
||||
} else sum.textContent = `${target.title} = ${unit}${explain(target.expr)}`;
|
||||
};
|
||||
setSum(known);
|
||||
const rate = el("div", { className: "m01lab__modal-body" });
|
||||
if (known && priceRef) {
|
||||
rate.append(priceView(priceRef, known));
|
||||
} else if (logicRef) {
|
||||
rate.append(el("p", { text: `${tl("Modal_Logic")} · ${logicRef}` }));
|
||||
} else if (logicKey) {
|
||||
rate.append(
|
||||
el("p", {
|
||||
children: [
|
||||
`${tl("Modal_Logic")} · `,
|
||||
fragmentRow(
|
||||
target.unitFrag ?? [
|
||||
{ pill: { label: logicKey, onClick: () => openLogic(logicKey) } },
|
||||
],
|
||||
),
|
||||
],
|
||||
}),
|
||||
);
|
||||
} else if (cond && priceRef) {
|
||||
rate.append(muted("…")); // 후보 목록이 오면 그 줄로 채움 — 100건 밖이면 키로 따로 찾아 옴
|
||||
} else if (cond) {
|
||||
@@ -335,7 +363,13 @@ export function openMaterialModal(target: ModalTarget): void {
|
||||
className: "m01lab__raw",
|
||||
children: [
|
||||
el("summary", { text: tl("Modal_Qty") }),
|
||||
el("pre", { className: "m01lab__expr", text: target.expr }),
|
||||
el("pre", {
|
||||
className: "m01lab__expr",
|
||||
text: logicKey
|
||||
? `${element}
|
||||
${target.expr}`
|
||||
: target.expr,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -113,6 +113,8 @@ export interface TextLine {
|
||||
이름글?: string;
|
||||
/** 수량 식을 쪼갠 조각 — 알약(요소·표·로직) · 글자(값·기호·글) */
|
||||
조각?: TextPiece[];
|
||||
/** 단가 자리(요소 · 로직 부르기)를 쪼갠 조각 — 줄 모달 윗줄 */
|
||||
요소조각?: TextPiece[];
|
||||
/** 줄 번호(답 전체에서 하나씩) — 두 칸 줄 맞춤 · 밝히기 */
|
||||
짝?: number;
|
||||
금액: number;
|
||||
|
||||
Reference in New Issue
Block a user