/* ============================================================================= * B07_DesignDetail_UI_Save.ts * 상세 설계 저장 · 원본 되돌림 묶음(요구 280-5 · 280-6 · 281) — `_UI_Page.ts` 가 700줄에 닿아 뗌. * * 저장 = 페이지를 옮길 때만([<] [>] · ☰ · 진행 단계 판 — `onStepClick` 한 길). * 고친 탭(●) + 지금 탭을 `confirm` 길로 · 안 고친 도면은 보내지 않음(늘 원본에서 새로 그림). * 잠금 없음 — 저장본도 그대로 고침 · 다시 옮길 때 다시 저장. * 원본 되돌림 = ⚙ 모달 [원본에서 다시 그리기] — 저장본이면 `invalidate` · 고친 것 버리고 다시 실음. * ========================================================================== */ import { hideLoadingOverlay, showConfirmDialog, showLoadingOverlay, showToast, } from "@ui/ui_template_elements"; import type { CadSaveResult } from "@ui/cad_host/cad_host"; import type { CadTabs } from "@ui/cad_host/cad_host_tabs"; import { confirmDesignDrawing, invalidateDesignDrawing, type CadDrawing, type DesignDrawingItem, type QuantityTable, } from "./B07_DesignDetail_Api_Fetch"; /** 화면 조립이 쥔 상태를 읽고 쓰는 길 — 이 묶음은 상태를 갖지 않음. */ export interface DrawingStore { projectId: string; drawings: DesignDrawingItem[]; tabs: CadTabs; current: () => DesignDrawingItem | undefined; /** CAD 에 저장 안 한 편집이 있나 · 지움 */ cadDirty: () => boolean; setCadClean: () => void; requestCad: () => Promise>; isFrameEditing: () => boolean; /** 도면 캐시에서 뺌 — 다음 열기에 서버에서 다시 받음 */ forget: (id: string) => void; /** 그 도면을 서버에서 다시 받아 실음 */ reload: (drawing: DesignDrawingItem) => Promise; } /** 좌측 단추 저장본 띠(초록) — 저장본 = 서버 `confirmed`. */ function markButton(id: string, saved: boolean): void { document .querySelectorAll(`.b07-drawing-button[data-drawing-id="${id}"]`) .forEach((button) => (button.dataset.confirmed = String(saved))); } /** * 고친 도면을 저장 — 지금 탭(CAD 에서 받음 · 횡단은 수량표 같이) + 다른 고친 탭(받아 둔 편집본). * 고친 것 없으면 요청 없이 참 · 하나라도 실패하면 알림 뒤 거짓(부르는 쪽은 머묾). */ export async function saveEditedDrawings(store: DrawingStore): Promise { if (store.isFrameEditing()) { showToast("도각 편집을 마친 뒤 옮기세요.", "warning"); return false; } const current = store.current(); const active = store.tabs.active(); const editable = (item: DesignDrawingItem | undefined): item is DesignDrawingItem => !!item && item.kind !== "blank"; const currentEdited = editable(current) && (store.cadDirty() || !!active?.dirty); const others = store.tabs .tabs() .filter((tab) => tab !== active && tab.dirty && tab.drawing) .map((tab) => ({ tab, item: store.drawings.find((item) => item.id === tab.id) })) .filter(({ item }) => editable(item)); if (!currentEdited && others.length === 0) return true; const save = async ( item: DesignDrawingItem, drawing: CadDrawing, table: QuantityTable | null, ) => { await confirmDesignDrawing(store.projectId, item.id, drawing, table); item.confirmed = true; markButton(item.id, true); store.forget(item.id); // 저장본이 정본 — 다음 열기는 서버에서 }; showLoadingOverlay(); try { if (currentEdited && current) { const saved = await store.requestCad(); await save( current, saved.drawing, current.kind === "cross" ? (saved.quantityTable ?? null) : null, ); store.setCadClean(); if (active) store.tabs.markClean(active); } for (const { tab, item } of others) { // 횡단 수량표는 서버가 그림의 표 글에서 다시 뽑음(받아 둔 편집본엔 수량표 칸이 없음) await save(item as DesignDrawingItem, tab.drawing as CadDrawing, null); store.tabs.markClean(tab); } return true; } catch (error) { showToast( `${error instanceof Error ? error.message : "도면을 저장하지 못했습니다."} — 이 화면에 머묾`, "error", ); return false; } finally { hideLoadingOverlay(); } } /** 지금 도면에 버릴 것(저장본 · 고친 것)이 있나 — ⚙ [원본에서 다시 그리기] 켬. */ export function hasRedrawable(store: DrawingStore): boolean { const current = store.current(); if (!current || current.kind === "blank") return false; return current.confirmed || store.cadDirty() || !!store.tabs.active()?.dirty; } /** ⚙ [원본에서 다시 그리기] — 물은 뒤 저장본 풂(`invalidate`) · 고친 것 버림 · 다시 실음. 해냈으면 참. */ export async function redrawFromSource(store: DrawingStore): Promise { const current = store.current(); if (!current || !hasRedrawable(store)) return false; const ok = await showConfirmDialog( `「${current.label}」 의 저장본 · 고친 것을 버리고 원본에서 다시 그릴까요?`, "다시 그리기", ); if (!ok) return false; showLoadingOverlay(); try { if (current.confirmed) { await invalidateDesignDrawing(store.projectId, current.id); current.confirmed = false; markButton(current.id, false); } store.forget(current.id); store.setCadClean(); const active = store.tabs.active(); if (active) store.tabs.markClean(active); await store.reload(current); showToast("원본에서 다시 그렸습니다.", "success"); return true; } catch (error) { showToast(error instanceof Error ? error.message : "원본에서 다시 그리지 못했습니다.", "error"); return false; } finally { hideLoadingOverlay(); } }