feat(804-6): 옛 수식 탭 걷음 · 줄 쌓기 = 첫 「수식」 탭(요구 686-1)
- M01 단가산출 기초 · M02 단가산출 상세 보기 = 수식(줄 쌓기 · 기본) · 단가산출 상세 둘 - 줄 쌓기에 M02 사본 [단가 갱신] · [재생성](미리 셈 → 알림 → 저장 · 고침 있으면 막음) · 판 바뀜(409) 문서 그대로면 새 판으로 다시 저장 옮김 - 옛 수식 문서 화면 걷음(formula_doc_screen · defs · edit · lines · math · math_model · rows · sub · formula_doc.css) · 이미 안 부르던 796-2 수식 나무 화면(LogicLab_Equation · Defs · Var · css · M01_Logic_Equation_Model · sheet_formula_cell · ui_template_equation_defs · edit) 걷음 — 18 파일 4,860 줄 - formula_doc_render = 줄 쌓기가 쓰는 reach 만 · 옵션 가로 CSS 는 line_stack.css 로 - 남김 = 계산기 common_util_formula_doc* · 서버 길 · 옵션 · 사본 표지 · 합계 상자 · KaTeX · 시험 페이지 tools/line-stack-test - 시험: test_796_2 지움(걷은 화면 원본 검사뿐) · 799_1 · 800_2 걷은 화면 줄만 뺌 · 799_2 · 802_4b2 · 804_2 보기 · 화면 파일 대상 바꿈 · test_804_6 새로(걷은 파일 · 부르는 곳 없음 · 첫 탭 · 사본 할일) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
/* =============================================================================
|
||||
* line_stack_screen.ts
|
||||
* 단가산출 「줄 쌓기」 탭(PLAN 804-2 · 요구 670~677) — 공용(M01 단가산출 기초 · M02 단가산출 = 길 앞머리만 바꿈)
|
||||
* 위 = 옵션 가로(수식 탭과 같은 부품 `formula_doc_options.ts`) · [고침 버림] · [수식 저장](관리자 · 층 쓰기)
|
||||
* 위 = 옵션 가로(`formula_doc_options.ts`) · [고침 버림] · [수식 저장](관리자 · 층 쓰기) · M02 사본 [단가 갱신] · [재생성]
|
||||
* 가운데 = 왼쪽 줄 쌓기 편집(`line_stack_edit.ts`) | 오른쪽 산출근거 출력(`line_stack_out.ts` · 기계 시간당 경비 펼침)
|
||||
* 아래 = 합계 상자(노무비 · 재료비 · 경비 · 계 — 수식 탭과 같은 부품)
|
||||
* 정본 = 수식 문서(수식 탭과 같은 문서 · 같은 길 · 같은 캐시 열쇠 — 탭을 바꾸면 다시 읽음) · 줄 쌓기 차림 = 문서 칸 `쌓기`
|
||||
* 아래 = 합계 상자(노무비 · 재료비 · 경비 · 계)
|
||||
* 정본 = 수식 문서(탭을 바꾸면 다시 읽음) · 줄 쌓기 차림 = 문서 칸 `쌓기` · 804-6 옛 수식 문서 탭 걷음 = 이 화면이 「수식」 탭
|
||||
* 셈 = 브라우저 계산기(`calcDoc`) 즉시 · 캐시에 없는 연결만 뒤에서 `POST {길}/preview`(옛 요청 끊음)
|
||||
* 고침 = 캐시(sessionStorage)에 쌓고 [수식 저장] 한 번에 `POST {길}/save`(서버가 정본으로 다시 셈) · 자동저장 없음
|
||||
* M02 사본(`copy`) — 저장 몸 {문서, 끊음(수식 탭이 끊은 문서 그대로), 판} · [단가 갱신] · [재생성]은 수식 탭
|
||||
* M02 사본(`copy`) — 저장 몸 {문서, 끊음(받은 그대로), 판} · 판 바뀜(409)은 문서가 그대로면 새 판으로 한 번 더
|
||||
* [단가 갱신] · [재생성] = 미리 셈 → 알림 → 저장(`formula_doc_copy.ts` · 고친 것 먼저 저장)
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
@@ -31,7 +32,15 @@ import {
|
||||
type StackDoc,
|
||||
type StackItem,
|
||||
} from "../common_util/common_util_line_stack";
|
||||
import { copyBadges, type CopyMark, type CopyOptions } from "./formula_doc_copy";
|
||||
import {
|
||||
askJob,
|
||||
copyBadges,
|
||||
copyTools,
|
||||
type CopyJob,
|
||||
type CopyMark,
|
||||
type CopyOptions,
|
||||
type JobReply,
|
||||
} from "./formula_doc_copy";
|
||||
import { docFoot } from "./formula_doc_foot";
|
||||
import { buildOptions } from "./formula_doc_options";
|
||||
import { ft } from "./formula_doc_text";
|
||||
@@ -40,14 +49,13 @@ import type { Layer } from "../M02_MasterTemplete/M02_MasterTemplete_Api_Fetch";
|
||||
import { buildEdit } from "./line_stack_edit";
|
||||
import { buildOut } from "./line_stack_out";
|
||||
import { lt } from "./line_stack_text";
|
||||
import "./formula_doc.css";
|
||||
import "./line_stack.css";
|
||||
|
||||
export interface StackScreenOptions {
|
||||
/** 길 앞머리(`/m01/logic/doc`) — GET ?key · POST /preview · /save · /links */
|
||||
base: string;
|
||||
admin: boolean;
|
||||
/** sessionStorage 열쇠 — 수식 탭과 같게(같은 문서 고침을 이어 받음) */
|
||||
/** sessionStorage 열쇠 */
|
||||
cache: string;
|
||||
copy?: CopyOptions;
|
||||
/** M02 SK 층(804-4) — 줄 쌓기 다른 단가산출 · 일위대가 복사 길 · 없으면(M01) 복사 없음 */
|
||||
@@ -122,7 +130,7 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
return data;
|
||||
};
|
||||
|
||||
// ── 캐시(수식 탭과 같은 열쇠) ──
|
||||
// ── 캐시 ──
|
||||
const drafts = (): Record<string, FormulaDoc> => {
|
||||
try {
|
||||
return JSON.parse(sessionStorage.getItem(opt.cache) ?? "{}") as Record<string, FormulaDoc>;
|
||||
@@ -191,7 +199,7 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
}
|
||||
};
|
||||
|
||||
/** 표 하나 — 수식 탭과 같은 길(`/table`) · 받은 표는 계산기 캐시에도 */
|
||||
/** 표 하나(`/table`) — 받은 표는 계산기 캐시에도 */
|
||||
const tableOf = (k: string): Promise<DocTable | null> => {
|
||||
if (!tables.has(k))
|
||||
tables.set(
|
||||
@@ -264,6 +272,7 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
const tools =
|
||||
edit() && key
|
||||
? [
|
||||
...(cp ? copyTools(true, (job) => void runJob(job)) : []),
|
||||
createButton({
|
||||
label: ft("Discard"),
|
||||
variant: "ghost",
|
||||
@@ -356,22 +365,44 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
version = got.판 ?? "";
|
||||
};
|
||||
|
||||
const save = async (): Promise<void> => {
|
||||
if (!doc || !dirty()) return;
|
||||
const kept = drafts()[`${key}#끊음`] as unknown as Docs | undefined; // 수식 탭이 끊은 문서 고침
|
||||
const send = cp ? { 문서: doc, 끊음: kept ?? cut, 판: version } : { 문서: doc };
|
||||
/** 사본 [단가 갱신] · [재생성] — 미리 셈 → 알림 → 저장(고친 것 먼저 저장 · 버림) */
|
||||
const runJob = async (job: CopyJob): Promise<void> => {
|
||||
if (!doc || !cp) return;
|
||||
if (dirty()) return void showToast(ft("Dirty_First"), "warning");
|
||||
showLoadingOverlay();
|
||||
let got: JobReply;
|
||||
try {
|
||||
got = await call<JobReply>("/preview", { 할일: job });
|
||||
} catch (e) {
|
||||
hideLoadingOverlay();
|
||||
return void showToast(
|
||||
`${ft("Save_Failed")} — ${e instanceof Error ? e.message : ""}`,
|
||||
"error",
|
||||
);
|
||||
}
|
||||
hideLoadingOverlay();
|
||||
if (await askJob(job, got, cache.names)) await save({ 할일: job, 판: version }, ft("Job_Done"));
|
||||
};
|
||||
|
||||
const save = async (job?: Record<string, unknown>, done = ft("Saved")): Promise<void> => {
|
||||
if (!doc || (!job && !dirty())) return;
|
||||
const send = job ?? (cp ? { 문서: doc, 끊음: cut, 판: version } : { 문서: doc });
|
||||
const was = saved;
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
const got = await call<Loaded>("/save", send);
|
||||
const got = await call<Loaded>("/save", send).catch(async (e: { status?: number }) => {
|
||||
// 사본 판 바뀜(페이지 [저장] 등) — 문서가 받은 그대로면 새 판으로 한 번 더
|
||||
if (!cp || e.status !== 409) throw e;
|
||||
const now = await call<Loaded>("");
|
||||
if (JSON.stringify(now.문서) !== was) throw e;
|
||||
return call<Loaded>("/save", { ...send, 판: (version = now.판 ?? "") });
|
||||
});
|
||||
take(got);
|
||||
const all = drafts();
|
||||
delete all[`${key}#끊음`];
|
||||
sessionStorage.setItem(opt.cache, JSON.stringify(all));
|
||||
keep();
|
||||
recalc();
|
||||
draw();
|
||||
if (cp && got.판) cp.onSaved?.(got.판);
|
||||
showToast(ft("Saved"), "success");
|
||||
showToast(done, "success");
|
||||
} catch (e) {
|
||||
showToast(`${ft("Save_Failed")} — ${e instanceof Error ? e.message : ""}`, "error");
|
||||
} finally {
|
||||
@@ -426,8 +457,6 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
sessionStorage.setItem(opt.cache, JSON.stringify(all));
|
||||
}
|
||||
if (all[next]) doc = clone(all[next] as StackDoc);
|
||||
if (cp && all[`${next}#끊음`])
|
||||
cache.merge({ 문서: (cut = clone(all[`${next}#끊음`] as unknown as Docs)) });
|
||||
recalc();
|
||||
draw();
|
||||
} catch (e) {
|
||||
@@ -443,7 +472,7 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
|
||||
const canLeave = async (): Promise<boolean> => {
|
||||
if (!dirty()) return true;
|
||||
if (!(key in drafts())) return true; // 수식 탭에서 이미 버림
|
||||
if (!(key in drafts())) return true; // 다른 화면에서 이미 버림
|
||||
if (!(await showConfirmDialog(ft("Leave"), ft("Leave_Ok")))) return false;
|
||||
doc = JSON.parse(saved) as StackDoc;
|
||||
keep();
|
||||
|
||||
Reference in New Issue
Block a user