/* ============================================================================= * A00_Common/design/design_save_api.ts * [저장] · [확정] 한 본문(PLAN 472-6 6-A) — 차례 조각 꼴 · 서버 한 창구 `POST /design/save`. * * 차례마다 조각 하나 = 본문(없으면 null · 서버가 그 차례를 건너뜀) + 저장 뒤 정리(초안 비움 · 화면 맞춤). * 서버는 전부 저장하거나 아무것도 안 바꿈 — 실패면 차례 이름 · 까닭(`restored` 거짓 = 복구 확인 필요). * 노선 바뀐 [저장](6-B) = 덧 칸 `route`(노선 초안) · `carry`(옛 노선 세션 칸) — 서버가 맨 앞에서 노선 저장 · 다시 얹기. * ========================================================================== */ import { API_ANALYSIS_TIMEOUT_MS, API_BASE_URL } from "@config/config_frontend"; /** 서버 본문 칸 차례 그대로(`B06_Section_Router_DesignSave`). */ export const SAVE_PART_KEYS = [ "settings", "pipes", "structures", "plusStations", "alignment", "culvertOptions", "uphill", "cross", ] as const; export type SavePartKey = (typeof SAVE_PART_KEYS)[number]; /** 차례 조각 — `after` 는 서버가 다 저장한 뒤에만(그 차례 응답을 받음). */ export interface SavePart { body: unknown | null; after?: (response: Record | undefined) => Promise | void; } /** 노선 저장 체인 시간 한도 — 옛 `/route/replan` 요청 한도와 같음(`B05_Profile_Api_Replan`). */ const ROUTE_SAVE_TIMEOUT_MS = 15 * 60 * 1000; const BODY_KEY: Record = { settings: "settings", pipes: "pipes", structures: "structures", plusStations: "plus_stations", alignment: "alignment", culvertOptions: "culvert_options", uphill: "uphill", cross: "cross", }; /** 서버 차례 이름 — 본문 칸 차례 + 노선(6-B · 맨 앞). */ export type SaveStepKey = SavePartKey | "route"; export interface DesignSaveResult { waited: boolean; /** 차례별 응답 · `route` = 노선 저장 결과 · `carry` = 넘김(`outside` = 노선 밖으로 버린 고침 수). */ steps: Partial>>; } /** 실패 — 차례 이름(`step`) · 되돌림 됨(`restored`). */ export class DesignSaveError extends Error { constructor( message: string, readonly step: SaveStepKey | null, readonly restored: boolean, ) { super(message); } } /** 조각 본문을 모아 한 번에 보냄 · `extra` = 노선 바뀐 [저장] 덧 칸(`route` · `carry`). */ export async function postDesignSave( projectId: string, routeId: number, confirm: boolean, parts: Partial>, extra: { route?: unknown; carry?: unknown } = {}, ): Promise { const body: Record = { route_id: routeId, confirm, ...extra }; for (const key of SAVE_PART_KEYS) { const part = parts[key]; if (part && part.body !== null && part.body !== undefined) body[BODY_KEY[key]] = part.body; } const controller = new AbortController(); // [확정]은 낡은 배수유역을 다시 셈 — 옛 차례들의 시간 한도 합 만큼 · 노선 저장이 끼면 그 한도(15 분)를 더함 const limit = API_ANALYSIS_TIMEOUT_MS * 3 + (extra.route ? ROUTE_SAVE_TIMEOUT_MS : 0); const timer = window.setTimeout(() => controller.abort(), limit); try { const response = await fetch(`${API_BASE_URL}/projects/${projectId}/design/save`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), signal: controller.signal, }); const payload = await response.json().catch(() => ({})); if (!response.ok) throw new DesignSaveError( payload?.message ?? `HTTP ${response.status}`, (payload?.step as SaveStepKey) ?? null, payload?.restored !== false, ); return { waited: payload?.waited === true, steps: payload?.steps ?? {} }; } catch (error) { if (error instanceof DOMException && error.name === "AbortError") throw new DesignSaveError( "저장 응답을 시간 안에 못 받았습니다 — 새로고침해 저장됐는지 확인해 주세요.", null, false, ); throw error; } finally { window.clearTimeout(timer); } }