- 공용 createPanelCard 곁값 onSettings — 캐럿 왼쪽 ⚙ · 누름이 접힘 안 일으킴(B08 · B09 그대로) - 평면도 ⚙ = 평면도 관리 모달([평면도 관리] 단추 지움) · 횡단도 ⚙ = 배치 칸 · 표 배율 · 안내선(카드 밑 칸 지움) · 구조물 ⚙ = 「M02 그림 — 축척 없음」 · 종단 1장 · 토적 = 단추 안 ⚙ · 표지 없음 - ⚙ 머리 [원본에서 다시 그리기] — 그 묶음 도면을 연 때만 · 저장본이면 invalidate - 잠금 없음(요구 281) — 읽기 전용 안 묶음 · 자동 B08 이동 · 「모든 횡단 확정」 잠금 지움 - onStepClick 한 길 = 고친 탭(●) + 지금 탭 저장 → B08 · B09 면 단계 4 완료(새 POST design-drawings-complete) → 이동 · 실패 = 머묾 - 저장 · 원본 묶음 = 새 B07_DesignDetail_UI_Save.ts · 모달 = 새 B07_DesignDetail_UI_Settings.ts(UI_Page 633 → 543줄) - 탭 줄 = .cad-host 의 --cad-tab-slot-left/right(px · CSS left · right 꼴) 있으면 리본 빈 자리에 겹침 · 없으면 지금 자리 - 시험: 새 test_280_5_b07_settings · 130 · 153-1 · 180-3 · 212-3 · 269 · 311 고쳐 씀 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148 lines
5.9 KiB
TypeScript
148 lines
5.9 KiB
TypeScript
/* =============================================================================
|
|
* 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<CadDrawing>;
|
|
current: () => DesignDrawingItem | undefined;
|
|
/** CAD 에 저장 안 한 편집이 있나 · 지움 */
|
|
cadDirty: () => boolean;
|
|
setCadClean: () => void;
|
|
requestCad: () => Promise<CadSaveResult<CadDrawing, QuantityTable>>;
|
|
isFrameEditing: () => boolean;
|
|
/** 도면 캐시에서 뺌 — 다음 열기에 서버에서 다시 받음 */
|
|
forget: (id: string) => void;
|
|
/** 그 도면을 서버에서 다시 받아 실음 */
|
|
reload: (drawing: DesignDrawingItem) => Promise<void>;
|
|
}
|
|
|
|
/** 좌측 단추 저장본 띠(초록) — 저장본 = 서버 `confirmed`. */
|
|
function markButton(id: string, saved: boolean): void {
|
|
document
|
|
.querySelectorAll<HTMLElement>(`.b07-drawing-button[data-drawing-id="${id}"]`)
|
|
.forEach((button) => (button.dataset.confirmed = String(saved)));
|
|
}
|
|
|
|
/**
|
|
* 고친 도면을 저장 — 지금 탭(CAD 에서 받음 · 횡단은 수량표 같이) + 다른 고친 탭(받아 둔 편집본).
|
|
* 고친 것 없으면 요청 없이 참 · 하나라도 실패하면 알림 뒤 거짓(부르는 쪽은 머묾).
|
|
*/
|
|
export async function saveEditedDrawings(store: DrawingStore): Promise<boolean> {
|
|
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<boolean> {
|
|
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();
|
|
}
|
|
}
|