feat(M02): 저장 안 한 고침 물음 단추 셋 — 취소 · 버리고 넘어감 · 저장 후 넘어감, settle 카드 저장 실패 흠(86-1)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
2026-10-08 12:01:53 +09:00
co-authored by Claude Sonnet 5.5
parent e25b9ddf2f
commit 245eaa77af
9 changed files with 267 additions and 71 deletions
@@ -6,6 +6,7 @@
import { setLeaveGuard } from "../A00_Common/router";
import { createButton, el, showConfirmDialog, showToast } from "@ui/ui_template_elements";
import { leaveOrStay } from "@ui/ui_template_leave_dialog";
import { loadingError, showLoading } from "@ui/ui_template_loading";
import { t as L } from "@ui/ui_template_locale";
import {
@@ -26,7 +27,6 @@ import {
type StructureColumn,
type StructurePanelHandle,
} from "./M02_MasterTemplete_Structure";
import { askSettle } from "./M02_MasterTemplete_TreePanel_Dialogs";
import { buildAreaPanel, type AreaPanel } from "./M02_MasterTemplete_AreaPanel";
import { createAreaToDrawing, sentRanges } from "./M02_MasterTemplete_AreaToDrawing";
import { detailHelp, HELP_TITLE } from "./M02_MasterTemplete_CardHelp";
@@ -202,19 +202,26 @@ export function createMain(
event.returnValue = "";
};
window.addEventListener("beforeunload", guard);
const confirmLeave = async (): Promise<boolean> => {
if (!busy()) return true; // 카드 고침(도면 · 엑셀)도 같은 물음
if (!(await showConfirmDialog(L("M02_DiscardConfirm")))) {
// 취소 — 초점을 시트(고른 칸 입력 자리) · 집계표 · 도면으로 돌려 키보드 그대로
host
.querySelector<HTMLElement>(".ss-input, .ui-sheet, iframe")
?.focus({ preventScroll: true });
return false;
}
dirty = false;
toDrawing.drop();
return true;
/**
* 저장 안 한 고침 물음(떠나기 · 나무 고치기 한 몸) — 단추 셋 [취소] [버리고 넘어감] [저장 후 넘어감].
* "clean" = 고침 없음 · "saved" · "discarded" · false = 머묾(취소 · 저장 실패 · 판 다름 · 카드 저장 실패).
* 열린 양식이 없는 화면(옵션 정의 등)은 저장할 것이 없어 2단추 그대로.
*/
const askUnsaved = async (message: string): Promise<false | "clean" | "saved" | "discarded"> => {
if (!busy()) return "clean"; // 카드 고침(도면 · 엑셀)도 같은 물음
const drop_ = (): void => {
dirty = false;
toDrawing.drop();
};
let got: "saved" | "discarded" | "stay";
if (sel) got = await leaveOrStay(message, { save: doSave, stillDirty: busy, discard: drop_ });
else got = (await showConfirmDialog(message)) ? (drop_(), "discarded") : "stay";
if (got !== "stay") return got;
// 머묾 — 초점을 시트(고른 칸 입력 자리) · 집계표 · 도면으로 돌려 키보드 그대로
host.querySelector<HTMLElement>(".ss-input, .ui-sheet, iframe")?.focus({ preventScroll: true });
return false;
};
const confirmLeave = async (): Promise<boolean> => !!(await askUnsaved(L("M02_DiscardConfirm")));
setLeaveGuard(confirmLeave); // 머리 메뉴 · 뒤로 가기로 떠날 때도 물음
const showNotice = (nodes: (HTMLElement | string)[]): void => {
@@ -573,15 +580,9 @@ export function createMain(
}
async function settle(): Promise<false | "clean" | "changed"> {
if (!busy()) return "clean";
const choice = await askSettle();
if (choice === "cancel") return false;
if (choice === "save") {
await doSave();
return dirty ? false : "changed"; // 저장이 막히면(판 다름 등) 나무도 안 고침
}
dirty = false; // 버림 — 화면을 서버 그대로 다시 읽음
if (sel) await open(sel);
const got = await askUnsaved(L("M02_TreeUnsaved"));
if (got === false || got === "clean") return got;
if (got === "discarded" && sel) await open(sel); // 버림 — 화면을 서버 그대로 다시 읽음
return "changed";
}