Files
Aislo/A00_Common/design/design_save_api.ts
T
eomsangdonandClaude Opus 5.5 63f994972c feat(472-6 6-A 브라우저 한 본문): 노선 안 바뀐 [저장] · [확정] = 차례 조각을 모아 /design/save 한 요청
- design_save: 노선 초안이 없으면 saveOneBody — 차례마다 조각(본문 · 저장 뒤 정리) · 서버가 다 저장한 뒤에만 정리(초안 비움 · 화면 맞춤) · 실패 = 차례 이름째 「아무것도 저장 안 함」 · 되돌림 실패 = 「복구 확인 필요」 · 기다림 알림
- 조각 함수: 페이지 설정 · 관 · 구조물(보기 목록) · 추가 측점 · 계획고(한 벌 종단도) · 관 옵션 · 상단측 · 횡단(횡단 보기) — 옛 flush 함수는 노선 바뀐 저장 길에 그대로(조각을 써서 같은 본문)
- 보기 몫 steps · cross → parts · crossPart · 노선 바뀐 저장 길은 옛 차례 그대로(6-B)
- 옛 9 차례를 단언하던 시험 10 개를 한 본문 꼴로(옛 PUT 줄 → SAVE 줄 · 조각 정리 차례) · 위키 [저장] 표

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:41:58 +09:00

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);
}
}