- M01 수식 탭 부품에 `page` 갈래 — 길 · 고침 목록 · 옵션 값을 페이지가 줌 · 저장 = 페이지 [저장] · M01 동작 그대로(판 바뀌면 다시 셈만 더함)
- 새 M02_MasterTemplete_Sangeun_Equation — 문서 `수식고침` 캐시 · 사본 표지 줄 · 가져온 식 「엑셀 원래」 · 일위대가 줄 모달 = 보기만
- 단가산출 상세 보기 = 수식(첫 · 기본) · 단가산출 상세 · 시트 · 옛 셋 뒤로(걷지 않음) · [저장] 뒤 할일 칸 뗌
- 낡은 시험 test_796_2 — 길 글이 앞머리 + 갈래로 바뀜(`/logic/equation${suffix}`) → 고침 · 화면 지킴 시험 하나 더함
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
174 lines
6.7 KiB
TypeScript
174 lines
6.7 KiB
TypeScript
/* =============================================================================
|
|
* 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);
|
|
}),
|
|
};
|
|
}
|