feat(796-2b): M02 단가산출 「수식」 첫 탭 — M01 수식 탭 부품 다시 씀(페이지 갈래) · 사본 [단가 갱신] · [재생성] · M01 새 판 배지 · 엑셀 원래
- M01 수식 탭 부품에 `page` 갈래 — 길 · 고침 목록 · 옵션 값을 페이지가 줌 · 저장 = 페이지 [저장] · M01 동작 그대로(판 바뀌면 다시 셈만 더함)
- 새 M02_MasterTemplete_Sangeun_Equation — 문서 `수식고침` 캐시 · 사본 표지 줄 · 가져온 식 「엑셀 원래」 · 일위대가 줄 모달 = 보기만
- 단가산출 상세 보기 = 수식(첫 · 기본) · 단가산출 상세 · 시트 · 옛 셋 뒤로(걷지 않음) · [저장] 뒤 할일 칸 뗌
- 낡은 시험 test_796_2 — 길 글이 앞머리 + 갈래로 바뀜(`/logic/equation${suffix}`) → 고침 · 화면 지킴 시험 하나 더함
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
* · 변수를 누르면 변수 창(`_EquationVar`) · 고친 목록(`edits`)은 캐시(sessionStorage)에 쌓고 [저장] 한 번에 `/equation/save`(개정 확인)
|
||||
* 계산 자리 = 서버 한 벌(③) — 화면은 고침 목록만 보내고 서버가 다시 셈한 나무를 다시 그림(옛 요청 끊음)
|
||||
* 시트 · 시험 쪽에 저장 안 한 고침이 있으면 수식 고침은 잠김(두 갈래가 한 줄을 따로 고치지 않게)
|
||||
* 페이지 갈래(`page` · 796-2b M02 단가산출 사본) = 같은 부품 · 길 · 고침 목록(문서 `수식고침`) · 옵션 값이 페이지 것 · 저장 = 페이지 [저장]
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
@@ -47,6 +48,8 @@ const TEXT = {
|
||||
Discard: ["수식 고침 버림", "Discard edits"],
|
||||
Add: ["변수 더함", "Add variable"],
|
||||
Edits: ["수식 고침 {n} 건 · 저장 안 함", "{n} equation edits · unsaved"],
|
||||
EditsPage: ["수식 고침 {n} 건 · 사본 위", "{n} equation edits · on copy"],
|
||||
DiscardPage: ["수식 원래로", "Back to copy"],
|
||||
Now: ["지금", "Now"],
|
||||
Before: ["고치기 전", "Before"],
|
||||
Blocked: [
|
||||
@@ -104,6 +107,19 @@ const loadCache = (): Record<string, EqEdit[]> => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 페이지 캐시 갈래(796-2b M02 단가산출 사본) — 길 · 고침 목록 · 옵션 값이 페이지 것 · 저장 = 페이지 [저장] */
|
||||
export interface EqPage {
|
||||
ask: <T>(
|
||||
suffix: "" | "/preview",
|
||||
body: Record<string, unknown>,
|
||||
signal?: AbortSignal,
|
||||
) => Promise<T>;
|
||||
edits: () => EqEdit[];
|
||||
keep: (edits: EqEdit[]) => void;
|
||||
values: () => Record<string, string>;
|
||||
setValue: (name: string, value: string | null) => void;
|
||||
}
|
||||
|
||||
export interface LabEquationOptions {
|
||||
/** 시스템 관리자 — 고침 도구 */
|
||||
admin: boolean;
|
||||
@@ -111,6 +127,8 @@ export interface LabEquationOptions {
|
||||
blocked: () => boolean;
|
||||
/** 저장 뒤 — 상세를 저장본으로 다시 엶 */
|
||||
onSaved: () => void;
|
||||
/** 없으면 M01(길 `/m01/logic/equation` · 캐시 sessionStorage · [수식 저장]) */
|
||||
page?: EqPage;
|
||||
}
|
||||
|
||||
export function createLabEquation(opt: LabEquationOptions) {
|
||||
@@ -139,9 +157,14 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
const open = new Set<string>();
|
||||
const drafts = new Map<string, string>();
|
||||
const canEdit = (): boolean => opt.admin && !opt.blocked();
|
||||
const page = opt.page;
|
||||
const vals = (): Record<string, string> => page?.values() ?? inputs;
|
||||
const ask = <T>(suffix: "" | "/preview", body: Record<string, unknown>, signal?: AbortSignal) =>
|
||||
page ? page.ask<T>(suffix, body, signal) : post<T>(`/logic/equation${suffix}`, body, signal);
|
||||
|
||||
const body0 = (): Record<string, unknown> => ({ key, inputs: inputsOf(inputs), 고침: edits });
|
||||
const body0 = (): Record<string, unknown> => ({ key, inputs: inputsOf(vals()), 고침: edits });
|
||||
const persist = (): void => {
|
||||
if (page) return page.keep(edits);
|
||||
const all = loadCache();
|
||||
if (edits.length) all[key] = edits;
|
||||
else delete all[key];
|
||||
@@ -157,9 +180,7 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
ctl?.abort();
|
||||
const mine = (ctl = new AbortController());
|
||||
try {
|
||||
const got = edits.length
|
||||
? await post<Read>("/logic/equation/preview", body0(), mine.signal)
|
||||
: await post<Read>("/logic/equation", body0(), mine.signal);
|
||||
const got = await ask<Read>(edits.length ? "/preview" : "", body0(), mine.signal);
|
||||
if (mine !== ctl) return;
|
||||
if (got.되나 === false || got.ok === false) {
|
||||
why = (got.까닭 ?? [got.reason ?? ""]).join(" · ");
|
||||
@@ -178,7 +199,7 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
|
||||
/** 고침 하나를 더해 보고 되면 굳힘 — 안 되면 까닭 목록 */
|
||||
async function commit(list: EqEdit[]): Promise<string[]> {
|
||||
const got = await post<Read>("/logic/equation/preview", {
|
||||
const got = await ask<Read>("/preview", {
|
||||
...body0(),
|
||||
고침: [...edits, ...list],
|
||||
}).catch((error: unknown): Read => ({
|
||||
@@ -214,6 +235,7 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
};
|
||||
const label = createText(spec.이름 + (spec.단위 ? ` (${spec.단위})` : ""), "muted", 12);
|
||||
const set = (v: string | number | null): void => {
|
||||
if (page) return page.setValue(spec.이름, v === null || v === "" ? null : String(v));
|
||||
if (v === null || v === "") delete inputs[spec.이름];
|
||||
else inputs[spec.이름] = String(v);
|
||||
askAgain();
|
||||
@@ -222,7 +244,7 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
if ((spec.고르기?.length ?? 0) > BIG) {
|
||||
// 찾기형 — 입력 + 목록(칠수록 좁혀짐)
|
||||
const box = createInputField({
|
||||
value: inputs[spec.이름] ?? "",
|
||||
value: vals()[spec.이름] ?? "",
|
||||
compact: true,
|
||||
size: 24,
|
||||
placeholder: auto === null || auto === undefined ? "" : String(auto),
|
||||
@@ -239,7 +261,7 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
});
|
||||
control = el("div", { children: [box.root, list] });
|
||||
} else {
|
||||
const made = createValueControl(item, inputs[spec.이름] ?? null, (v) => set(v));
|
||||
const made = createValueControl(item, vals()[spec.이름] ?? null, (v) => set(v));
|
||||
const tip = (): void => {
|
||||
const c = made.control;
|
||||
c.title = c instanceof HTMLSelectElement ? (c.selectedOptions[0]?.text ?? "") : c.value;
|
||||
@@ -283,8 +305,8 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
const check =
|
||||
(line: EqLine) =>
|
||||
async (text: string, signal: AbortSignal): Promise<FormulaVerdict> => {
|
||||
const got = await post<Read>(
|
||||
"/logic/equation/preview",
|
||||
const got = await ask<Read>(
|
||||
"/preview",
|
||||
{ ...body0(), 고침: [...edits, formulaEdit(line, text)] },
|
||||
signal,
|
||||
);
|
||||
@@ -369,15 +391,17 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
if (!v || !canEdit()) return;
|
||||
openVarDialog({
|
||||
row: v,
|
||||
value: inputs[v.열쇠] ?? "",
|
||||
value: vals()[v.열쇠] ?? "",
|
||||
commit,
|
||||
onValue: (value) => {
|
||||
if (page) return page.setValue(v.열쇠, value || null);
|
||||
if (value) inputs[v.열쇠] = value;
|
||||
else delete inputs[v.열쇠];
|
||||
drawOptions();
|
||||
void refresh();
|
||||
},
|
||||
onDropped: (k) => {
|
||||
if (page) return page.setValue(k, null);
|
||||
delete inputs[k];
|
||||
drawOptions();
|
||||
},
|
||||
@@ -403,14 +427,16 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
items.push("spacer");
|
||||
if (edits.length) {
|
||||
items.push(
|
||||
createBadge(t("Edits", edits.length), "warning"),
|
||||
createBadge(t(page ? "EditsPage" : "Edits", edits.length), "warning"),
|
||||
createButton({
|
||||
label: t("Discard"),
|
||||
label: t(page ? "DiscardPage" : "Discard"),
|
||||
variant: "ghost",
|
||||
size: 24,
|
||||
onClick: () => void discard(),
|
||||
}),
|
||||
createButton({ label: t("Save"), size: 24, onClick: () => void save() }),
|
||||
...(page
|
||||
? []
|
||||
: [createButton({ label: t("Save"), size: 24, onClick: () => void save() })]),
|
||||
);
|
||||
} else items.push(createText(t("Hint"), "muted", 12));
|
||||
}
|
||||
@@ -488,17 +514,21 @@ export function createLabEquation(opt: LabEquationOptions) {
|
||||
root,
|
||||
/** 상세가 그릴 때마다 — 로직이 바뀔 때만 서버를 부름 */
|
||||
draw: (nextKey: string, nextVersion: string, nextRow: LogicRow): void => {
|
||||
const moved = nextVersion !== version; // 판 · 페이지 문서 · 옵션 바뀜 = 다시 셈
|
||||
version = nextVersion;
|
||||
row = nextRow;
|
||||
if (nextKey !== key) {
|
||||
[key, eq, rowNow, before, why, selected] = [nextKey, null, null, null, "", null];
|
||||
inputs = {};
|
||||
edits = loadCache()[nextKey] ?? [];
|
||||
edits = page ? page.edits() : (loadCache()[nextKey] ?? []);
|
||||
open.clear();
|
||||
drafts.clear();
|
||||
paint();
|
||||
drawOptions();
|
||||
void refresh().then(drawOptions);
|
||||
} else if (moved) {
|
||||
if (page) edits = page.edits();
|
||||
void refresh().then(drawOptions);
|
||||
} else if (opt.blocked() !== paintedBlocked) paint();
|
||||
},
|
||||
dirty: (): boolean => edits.length > 0,
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Sangeun_Equation.ts
|
||||
* M02 단가산출(SK) 「수식」 보기(PLAN 796-2b · 요구 655 · 656 · 657 · 요구서_655 0 장) — M01 수식 탭 부품(`createLabEquation`)을 페이지 갈래로.
|
||||
* 길 = `POST /layers/{층}/sangeun/equation`(+ `/preview`) · 몸 = 화면 문서(캐시) · 고침 목록 = 문서 `수식고침`(사본 위 기록) ·
|
||||
* 저장 = 상세 [저장](서버가 다시 얹어 검사 · 셈) · 옵션 = 상세 옵션 값(`onInput`)
|
||||
* 위 줄 = 로직 사본 — 「M01 새 판 있음」 배지 · [단가 갱신] · [재생성](`…/sangeun/copy/price|regen` → 문서 `사본할일` · [저장] 때 서버 적용)
|
||||
* 가져온 식 = 「엑셀 원래」 금액 글(셈 답 `원래` = 엑셀 금액)
|
||||
* ========================================================================== */
|
||||
|
||||
import { createBadge } from "@ui/ui_template_badge";
|
||||
import { createButton } from "@ui/ui_template_button";
|
||||
import { el, showToast } from "@ui/ui_template_elements";
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createToolbar } from "@ui/ui_template_toolbar";
|
||||
import { formatMoney } from "../common_util/common_util_money";
|
||||
import type { LogicRow } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
|
||||
import { createLabEquation } from "../M01_MasterData/M01_MasterData_UI_LogicLab_Equation";
|
||||
import type { EqEdit } from "../M01_MasterData/logic/M01_Logic_Equation_Model";
|
||||
import { call, type Layer } from "./M02_MasterTemplete_Api_Fetch";
|
||||
import type { SkAnswer, SkDoc } from "./logic/M02_Sangeun_Cover_Model";
|
||||
|
||||
const TEXT = {
|
||||
Copy: ["로직 사본", "Logic copy"],
|
||||
NewM01: ["M01 새 판 있음", "New M01 version"],
|
||||
Price: ["단가 갱신", "Refresh prices"],
|
||||
Regen: ["재생성", "Regenerate"],
|
||||
Job: ["{n} · [저장] 때 적용", "{n} · applied on save"],
|
||||
Excel: ["엑셀 원래", "Excel original"],
|
||||
Lost: ["못 얹은 고침", "Not re-applied"],
|
||||
} as const satisfies Record<string, readonly [string, string]>;
|
||||
const t = (k: keyof typeof TEXT, n = ""): string =>
|
||||
(TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0]).replace("{n}", n);
|
||||
|
||||
const KEY = "수식고침";
|
||||
const JOB = "사본할일";
|
||||
|
||||
interface CopyMark {
|
||||
M01새판?: boolean;
|
||||
지운키?: string[];
|
||||
}
|
||||
type Answer = SkAnswer & {
|
||||
사본?: CopyMark;
|
||||
원래?: Record<string, number> | null;
|
||||
가져온?: unknown;
|
||||
};
|
||||
interface CopyReply {
|
||||
문서: SkDoc;
|
||||
전: Record<string, number> | null;
|
||||
후: Record<string, number> | null;
|
||||
바뀐자원?: unknown[];
|
||||
못얹은?: { cid: string; 까닭: string }[];
|
||||
}
|
||||
|
||||
export interface SkEquationOptions {
|
||||
scope: { layer: Layer; owner: string | null };
|
||||
edit: boolean;
|
||||
touch: () => void;
|
||||
onInput: (name: string, value: string) => void;
|
||||
}
|
||||
|
||||
export interface SkEquationDraw {
|
||||
doc: SkDoc;
|
||||
ans: SkAnswer | null;
|
||||
base: LogicRow | null;
|
||||
values: Record<string, string>;
|
||||
}
|
||||
|
||||
export function createSangeunEquation(opt: SkEquationOptions) {
|
||||
const { layer, owner } = opt.scope;
|
||||
const q = owner && layer !== "system" ? `?owner=${encodeURIComponent(owner)}` : "";
|
||||
const bar = el("div", { attrs: { "data-sk-copy": "" } });
|
||||
let last: SkEquationDraw | null = null;
|
||||
const doc = (): SkDoc => last?.doc ?? ({} as SkDoc);
|
||||
/** 고침 = 저장되는 SK 만(일위대가 줄 모달 `줄모드` = 보기만 — 줄로 못 옮김) · 처음 그릴 때 정함 */
|
||||
const make = (d: SkEquationDraw) =>
|
||||
createLabEquation({
|
||||
admin: opt.edit && d.doc.줄모드 !== true,
|
||||
blocked: () => false,
|
||||
onSaved: () => undefined,
|
||||
page: {
|
||||
ask: (suffix, body, signal) =>
|
||||
call(`/layers/${layer}/sangeun/equation${suffix}${q}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ...body, 문서: doc() }),
|
||||
signal,
|
||||
}),
|
||||
edits: () => (doc()[KEY] as EqEdit[] | undefined) ?? [],
|
||||
keep: (edits) => {
|
||||
if (edits.length) doc()[KEY] = edits;
|
||||
else delete doc()[KEY];
|
||||
opt.touch();
|
||||
},
|
||||
values: () => last?.values ?? {},
|
||||
setValue: (name, value) => opt.onInput(name, value ?? ""),
|
||||
},
|
||||
});
|
||||
let lab: ReturnType<typeof make> | null = null;
|
||||
const root = el("div", { attrs: { "data-sk-equation": "" }, children: [bar] });
|
||||
|
||||
async function job(how: "price" | "regen"): Promise<void> {
|
||||
try {
|
||||
const got = await call<CopyReply>(`/layers/${layer}/sangeun/copy/${how}${q}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ 문서: doc(), 입력값: last?.values ?? {} }),
|
||||
});
|
||||
doc()[JOB] = got.문서[JOB];
|
||||
const sum = (v: Record<string, number> | null): string => formatMoney(v?.계 ?? 0);
|
||||
const lost = got.못얹은 ?? [];
|
||||
showToast(
|
||||
`${t(how === "price" ? "Price" : "Regen")} ${sum(got.전)} → ${sum(got.후)}` +
|
||||
(lost.length ? ` · ${t("Lost")} ${lost.map((x) => x.cid).join(" · ")}` : ""),
|
||||
lost.length ? "warning" : "success",
|
||||
);
|
||||
opt.touch();
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : String(error), "error");
|
||||
}
|
||||
}
|
||||
|
||||
function drawBar(d: SkEquationDraw): void {
|
||||
const ans = d.ans as Answer | null;
|
||||
const mark = ans?.사본;
|
||||
const pending = d.doc[JOB] as string | undefined;
|
||||
const items: (HTMLElement | "divider" | "spacer")[] = [];
|
||||
if (ans && "가져온" in ans)
|
||||
items.push(createText(`${t("Excel")} ${formatMoney(ans.원래?.계 ?? 0)}`, "muted", 14));
|
||||
if (mark) {
|
||||
items.push(createText(t("Copy"), "muted", 14));
|
||||
if (mark.M01새판) items.push(createBadge(t("NewM01"), "warning"));
|
||||
if (pending) items.push(createBadge(t("Job", pending), "info"));
|
||||
if (opt.edit) {
|
||||
items.push(
|
||||
"spacer",
|
||||
createButton({
|
||||
label: t("Price"),
|
||||
variant: "ghost",
|
||||
size: 24,
|
||||
onClick: () => void job("price"),
|
||||
}),
|
||||
createButton({
|
||||
label: t("Regen"),
|
||||
variant: "ghost",
|
||||
size: 24,
|
||||
onClick: () => void job("regen"),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
bar.replaceChildren(
|
||||
...(items.length ? [createToolbar({ items, size: 28, label: t("Copy") })] : []),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
root,
|
||||
draw: (d: SkEquationDraw): void => {
|
||||
last = d;
|
||||
drawBar(d);
|
||||
if (!d.base) return;
|
||||
if (!lab) root.append((lab = make(d)).root);
|
||||
const { [KEY]: _edits, ...rest } = d.doc;
|
||||
lab.draw(String(d.doc.키 ?? ""), JSON.stringify([rest, d.values]), d.base);
|
||||
},
|
||||
/** [저장] 뒤 — 할일 칸은 서버가 적용 · 뗌(상세 `getDoc` 이 복사한 뒤) */
|
||||
saved: (): void =>
|
||||
queueMicrotask(() => {
|
||||
if (!last?.doc[JOB]) return;
|
||||
delete last.doc[JOB];
|
||||
drawBar(last);
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Sangeun_Sheet.ts
|
||||
* M02 단가산출(SK) 줄 식 시트(PLAN 790-1 · E1 · 요구 650~652 · 요구서_652 1 장 4) — 상세 첫 탭 「시트」.
|
||||
* 보기 고르기 = 시트 · 옛 보기 넷(단가산출 상세 · 인력·재료·경비 · 시험 계산 · 텍스트 수식 — E7 까지 둠)
|
||||
* 보기 고르기 = 수식(첫 탭 · 796-2b `_Sangeun_Equation`) · 단가산출 상세 · 시트 · 옛 보기 셋(인력·재료·경비 · 시험 계산 · 텍스트 수식 — 뒤로만)
|
||||
* 시트 = 한 줄 「이름 | 식 | 값 | 비목」(줄 꼴 = `logic/M02_Sangeun_Sheet_Model`) · 안 보기 전환 「시트 ↔ 산출서」
|
||||
* 고침 = 지금 되는 것만(모델 안 바꿈) — 옵션 고름 · 품 · 단가 · 율 % · 줄 빼기 · 되살림 · [+ 자원 줄] · [+ 율 줄]
|
||||
* → SK 문서(덮개 · 더한줄 · 뺀줄 = `M02_Sangeun_Cover_Model`) → 상세 `touch` = 서버 셈 한 번(최신 응답만)
|
||||
@@ -24,6 +24,7 @@ import { formatLineMoney, formatMoney } from "../common_util/common_util_money";
|
||||
import type { LogicRow, TextAnswer } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
|
||||
import { tl } from "../M01_MasterData/M01_MasterData_UI_LogicLab_Text";
|
||||
import { removedStrip } from "./M02_MasterTemplete_Sangeun_Cover";
|
||||
import { createSangeunEquation } from "./M02_MasterTemplete_Sangeun_Equation";
|
||||
import {
|
||||
addTopNote,
|
||||
formulaEditor,
|
||||
@@ -130,14 +131,16 @@ export interface SheetOptions {
|
||||
scope: { layer: Layer; owner: string | null };
|
||||
}
|
||||
|
||||
type View = "sheet" | "ho" | "est" | "calc" | "formula";
|
||||
type View = "equation" | "sheet" | "ho" | "est" | "calc" | "formula";
|
||||
|
||||
/** 보기 고르기(시트 + 옛 넷) · 시트 그릇 — `editor` = 옛 보기 그릇(시트일 때 숨김) */
|
||||
export function createSangeunSheet(
|
||||
editor: HTMLElement,
|
||||
opt: SheetOptions,
|
||||
): { picker: HTMLElement; host: HTMLElement; draw: (d: SheetDraw) => void; saved: () => void } {
|
||||
const host = el("div", { className: "m02-sks", attrs: { "data-sk-sheet": "" } });
|
||||
const sheetHost = el("div", { className: "m02-sks", attrs: { "data-sk-sheet": "" } });
|
||||
const eqView = createSangeunEquation(opt); // 796-2b 수식 첫 탭
|
||||
const host = el("div", { children: [eqView.root, sheetHost] });
|
||||
const status = el("div", { className: "m02-sks__status", attrs: { "data-sk-status": "" } });
|
||||
const body = el("div", { className: "m02-sks__body" });
|
||||
let mode: "sheet" | "paper" = "sheet";
|
||||
@@ -148,19 +151,21 @@ export function createSangeunSheet(
|
||||
const drafts = new Map<string, string>(); // 치는 중 식 글
|
||||
let focusNext = ""; // 새 글 줄 칸 — 다음 그림 뒤 초점
|
||||
const show = (v: View): void => {
|
||||
host.hidden = v !== "sheet";
|
||||
editor.hidden = v === "sheet";
|
||||
if (v !== "sheet") editor.dataset.view = v;
|
||||
eqView.root.hidden = v !== "equation";
|
||||
sheetHost.hidden = v !== "sheet";
|
||||
editor.hidden = v === "sheet" || v === "equation";
|
||||
if (!editor.hidden) editor.dataset.view = v;
|
||||
};
|
||||
const picker = createSegment({
|
||||
options: [
|
||||
{ value: "sheet", label: t("Sheet") },
|
||||
{ value: "equation", label: tl("View_Equation") },
|
||||
{ value: "ho", label: tl("Ho_Title") },
|
||||
{ value: "sheet", label: t("Sheet") },
|
||||
{ value: "est", label: tl("View_Est") },
|
||||
{ value: "calc", label: tl("Calc_Title") },
|
||||
{ value: "formula", label: tl("Formula_Title") },
|
||||
],
|
||||
value: "sheet",
|
||||
value: "equation",
|
||||
size: 32,
|
||||
ariaLabel: tl("View_Label"),
|
||||
onChange: (v) => show(v as View),
|
||||
@@ -180,8 +185,11 @@ export function createSangeunSheet(
|
||||
},
|
||||
});
|
||||
inner.root.dataset.skMode = "";
|
||||
host.append(createToolbar({ items: [inner.root, "divider", status], label: t("Mode") }), body);
|
||||
show("sheet");
|
||||
sheetHost.append(
|
||||
createToolbar({ items: [inner.root, "divider", status], label: t("Mode") }),
|
||||
body,
|
||||
);
|
||||
show("equation");
|
||||
|
||||
function drawStatus(d: SheetDraw): void {
|
||||
const s = statusOf(d.ans, d.bare, before);
|
||||
@@ -447,6 +455,7 @@ export function createSangeunSheet(
|
||||
|
||||
function draw(d: SheetDraw): void {
|
||||
last = d;
|
||||
eqView.draw(d);
|
||||
if (before === null && d.ans?.기준?.계 != null) before = d.ans.기준.계;
|
||||
ctx = {
|
||||
doc: d.doc as ConstDoc,
|
||||
@@ -461,7 +470,8 @@ export function createSangeunSheet(
|
||||
};
|
||||
drawStatus(d);
|
||||
const active = document.activeElement as HTMLElement | null;
|
||||
const keep = focusNext || (active && host.contains(active) ? active.dataset.sheetCell : "");
|
||||
const keep =
|
||||
focusNext || (active && sheetHost.contains(active) ? active.dataset.sheetCell : "");
|
||||
focusNext = "";
|
||||
body.dataset.mode = mode;
|
||||
body.replaceChildren(
|
||||
@@ -479,6 +489,7 @@ export function createSangeunSheet(
|
||||
host,
|
||||
draw,
|
||||
saved: () => {
|
||||
eqView.saved(); // 796-2b 사본 할일 뗌
|
||||
before = last?.ans?.기준?.계 ?? null; // 저장 = 고치기 전 새로(상태줄만 다시)
|
||||
if (last) drawStatus(last);
|
||||
},
|
||||
|
||||
@@ -59,8 +59,9 @@ def test_계산_자리_화면은_모양만():
|
||||
assert "fetch(" not in _text(path) # 부품은 그리기만
|
||||
page = _text(PAGE[0])
|
||||
assert page.count("fetch(") == 1 # 서버 길 한 곳 — 계산은 서버 한 벌(③)
|
||||
for route in ('"/logic/equation"', '"/logic/equation/preview"', '"/logic/equation/save"'):
|
||||
assert route in page, route
|
||||
for route in ("`/logic/equation${suffix}`", '"/preview"', '"/logic/equation/save"'):
|
||||
assert route in page, route # 796-2b 길 앞머리 = M01 · 페이지 갈래(page.ask)
|
||||
assert "page ? page.ask<T>(suffix, body, signal)" in page
|
||||
assert "saveRevised(" in page and "개정확인" in page # [저장] = 개정 확인 길
|
||||
assert "edits" in page and "sessionStorage" in page # 고침 목록 = 캐시 → [저장] 한 번
|
||||
assert "ctl?.abort()" in page # 옛 요청 끊음
|
||||
|
||||
@@ -175,3 +175,18 @@ def test_m01_change_kept_regen_reapplies(world, monkeypatch):
|
||||
got = skc.preview(_doc(me, key), skc.REGEN)
|
||||
assert eqm.LOST in [x["cid"] for x in got["못얹은"]] # 그 밖 = 없어진 줄 M01판(790-2)
|
||||
assert m01.loaded()[1].logic(KEY)["호표"] == []
|
||||
|
||||
|
||||
def test_screen_same_part_first_tab():
|
||||
"""화면 — M02 수식 = M01 수식 탭 부품 다시 씀(복사 없음) · 첫 탭 · 옛 탭 뒤로 · 저장 = 페이지."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(__file__).resolve().parents[2] / "M02_MasterTemplete"
|
||||
mine = (root / "M02_MasterTemplete_Sangeun_Equation.ts").read_text(encoding="utf-8")
|
||||
assert "createLabEquation(" in mine and "fetch(" not in mine and "createEqChain" not in mine
|
||||
assert "/sangeun/equation${suffix}" in mine and "/sangeun/copy/${how}" in mine
|
||||
sheet = (root / "M02_MasterTemplete_Sangeun_Sheet.ts").read_text(encoding="utf-8")
|
||||
views = re.findall(r'\{ value: "(\w+)", label: ', sheet)[:6]
|
||||
assert views == ["equation", "ho", "sheet", "est", "calc", "formula"]
|
||||
assert 'show("equation");' in sheet
|
||||
|
||||
Reference in New Issue
Block a user