feat(804-2): 단가산출 「줄 쌓기」 새 탭 — M01 단가산출 기초 · M02 단가산출 · 기계 시간당 경비 수식 펼침 · KaTeX

- 공용 화면 A00_Common/line_stack_* — 위 옵션 · 왼쪽 줄 쌓기 편집 · 오른쪽 산출근거(경쟁사 시트 꼴) · 맨 아래 합계 상자
- 모델 common_util_line_stack.ts — 수식 문서 ↔ 줄 쌓기 한 벌 왕복(차림 = 문서 칸 `쌓기` · 기본과 다를 때만)
- 기계 · 단가산출 줄 단가 수 누름 = 그 문서를 같은 계산기로 셈해 펼침(손료 식 · 운전경비 · 노 · 재 · 경 · 계)
- 공식 묶음 · 틀 풀기(단가산출 결과 → 이 문서 식) · 내 틀(이 브라우저) · 「내 틀 (… 에서 변경)」
- 수식 탭과 같은 길 · 같은 캐시 · 탭 바꾸면 다시 읽음 · [수식 저장] = 서버 정본 셈
- KaTeX(MIT) — vite 미리 묶기에서 뺌(rolldown 이 글자 규칙 서로게이트 범위를 깨뜨림)
- 시험 test_804_2 6(왕복 1,518 · 금액 · 기계 펼침 = hourly · 틀 풀기 · 저장 · 이음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-08 00:34:29 +09:00
co-authored by Claude Opus 5.5
parent 26bd46041e
commit c11baa441c
19 changed files with 2657 additions and 14 deletions
+18 -4
View File
@@ -44,6 +44,7 @@ import { saveRevised } from "./M01_MasterData_UI_LogicLab_Revise";
import { buildLogicSide } from "./M01_MasterData_UI_LogicLab_Side";
import { createViewPicker } from "./M01_MasterData_UI_LogicLab_View";
import { createDocScreen } from "../A00_Common/formula_doc_screen";
import { createStackScreen } from "../A00_Common/line_stack_screen";
import { buildHeadBar } from "./M01_MasterData_UI_Head";
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
import "./M01_MasterData_UI_Logic_Style.css";
@@ -109,10 +110,21 @@ export async function mountM01LogicLab(
const drive = createDrive();
// 802-3 수식 = 수식 문서 화면(공용 · 옛 수식 나무 화면은 M01 에서 걷음)
const equation = createDocScreen({ base: "/m01/logic/doc", admin, cache: "m01_doc_drafts" });
// 804-2 줄 쌓기 = 같은 수식 문서 · 같은 캐시 — 탭을 바꾸면 고른 쪽이 다시 읽음
const stack = createStackScreen({ base: "/m01/logic/doc", admin, cache: "m01_doc_drafts" });
const leave = (): Promise<boolean> => equation.canLeave().then((ok) => ok && stack.canLeave());
const editor = el("div", { className: "m01-logic__editor" });
// 로직 상세 — 왼쪽 지금 내용 · 오른쪽 접는 패널 「원문」
const panes = buildLogicSide(editor);
const views = createViewPicker(editor); // 보기 고르기 — 머리줄 [신규 로직] 왼쪽
// 보기 고르기 — 머리줄 [신규 로직] 왼쪽
const views = createViewPicker(editor, (v) => {
const k = opened?.origKey;
if (v === "stack") stack.draw(k ?? "", true);
else if (v === "equation" && k) {
equation.draw("");
equation.draw(k);
}
});
const errors = el("div", {
className: "ui-banner ui-banner--danger m01-logic__reasons",
attrs: { hidden: "" },
@@ -128,7 +140,7 @@ export async function mountM01LogicLab(
const back = createButton({
label: tx("Bar_Back"),
variant: "ghost",
onClick: () => void equation.canLeave().then((ok) => ok && show(null)),
onClick: () => void leave().then((ok) => ok && show(null)),
});
const persist = (): void => {
@@ -243,8 +255,10 @@ export async function mountM01LogicLab(
rerun: () => void drive.run(),
admin,
equation: equation.root,
stack: stack.root,
});
equation.draw(current.origKey ?? "");
stack.draw(current.origKey ?? "");
};
/** 단가산출 상세 옵션 입력 → 시험 입력(이름이 있는 칸만) · 바뀌면 다시 셈 */
@@ -320,7 +334,7 @@ export async function mountM01LogicLab(
restore: true,
onPick: (sub, detail) => {
list.setPick(sub, detail);
if (opened) void equation.canLeave().then((ok) => ok && show(null));
if (opened) void leave().then((ok) => ok && show(null));
},
}),
);
@@ -501,7 +515,7 @@ export async function mountM01LogicLab(
show(null);
showLoadingOverlay();
const openKeyOn = async (key: string): Promise<void> => {
if (!(await equation.canLeave())) return;
if (!(await leave())) return;
const found = allLogics.find((x) => x.키 === key);
if (found) await open(logicId(found.구분, found.키), found.구분, found.키);
else showToast(tx("Load_Failed"), "error");