- design_save: 노선 초안이 없으면 saveOneBody — 차례마다 조각(본문 · 저장 뒤 정리) · 서버가 다 저장한 뒤에만 정리(초안 비움 · 화면 맞춤) · 실패 = 차례 이름째 「아무것도 저장 안 함」 · 되돌림 실패 = 「복구 확인 필요」 · 기다림 알림 - 조각 함수: 페이지 설정 · 관 · 구조물(보기 목록) · 추가 측점 · 계획고(한 벌 종단도) · 관 옵션 · 상단측 · 횡단(횡단 보기) — 옛 flush 함수는 노선 바뀐 저장 길에 그대로(조각을 써서 같은 본문) - 보기 몫 steps · cross → parts · crossPart · 노선 바뀐 저장 길은 옛 차례 그대로(6-B) - 옛 9 차례를 단언하던 시험 10 개를 한 본문 꼴로(옛 PUT 줄 → SAVE 줄 · 조각 정리 차례) · 위키 [저장] 표 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
/* =============================================================================
|
|
* A00_Common/design/design_save_api.ts
|
|
* [저장] · [확정] 한 본문(PLAN 472-6 6-A) — 차례 조각 꼴 · 서버 한 창구 `POST /design/save`.
|
|
*
|
|
* 차례마다 조각 하나 = 본문(없으면 null · 서버가 그 차례를 건너뜀) + 저장 뒤 정리(초안 비움 · 화면 맞춤).
|
|
* 서버는 전부 저장하거나 아무것도 안 바꿈 — 실패면 차례 이름 · 까닭(`restored` 거짓 = 복구 확인 필요).
|
|
* ========================================================================== */
|
|
|
|
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<string, unknown> | undefined) => Promise<void> | void;
|
|
}
|
|
|
|
const BODY_KEY: Record<SavePartKey, string> = {
|
|
settings: "settings",
|
|
pipes: "pipes",
|
|
structures: "structures",
|
|
plusStations: "plus_stations",
|
|
alignment: "alignment",
|
|
culvertOptions: "culvert_options",
|
|
uphill: "uphill",
|
|
cross: "cross",
|
|
};
|
|
|
|
export interface DesignSaveResult {
|
|
waited: boolean;
|
|
steps: Partial<Record<SavePartKey, Record<string, unknown>>>;
|
|
}
|
|
|
|
/** 실패 — 차례 이름(`step`) · 되돌림 됨(`restored`). */
|
|
export class DesignSaveError extends Error {
|
|
constructor(
|
|
message: string,
|
|
readonly step: SavePartKey | null,
|
|
readonly restored: boolean,
|
|
) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
/** 조각 본문을 모아 한 번에 보냄. */
|
|
export async function postDesignSave(
|
|
projectId: string,
|
|
routeId: number,
|
|
confirm: boolean,
|
|
parts: Partial<Record<SavePartKey, SavePart | null>>,
|
|
): Promise<DesignSaveResult> {
|
|
const body: Record<string, unknown> = { route_id: routeId, confirm };
|
|
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();
|
|
// [확정]은 낡은 배수유역을 다시 셈 — 옛 차례들의 시간 한도 합 만큼
|
|
const timer = window.setTimeout(() => controller.abort(), API_ANALYSIS_TIMEOUT_MS * 3);
|
|
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 SavePartKey) ?? 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);
|
|
}
|
|
}
|