Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1
This commit is contained in:
@@ -23,7 +23,11 @@ import {
|
||||
toDesignProfile,
|
||||
} from "../../B05_Profile/logic/B05_Profile_UI_Profile_Data";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { saveProfileAlignment } from "../../B05_Profile/B05_Profile_Api_Fetch";
|
||||
import {
|
||||
saveProfileAlignment,
|
||||
type ProfileAlignmentSaveResponse,
|
||||
} from "../../B05_Profile/B05_Profile_Api_Fetch";
|
||||
import type { SavePart } from "./design_save_api";
|
||||
import { hasStaleDesigns } from "../../B06_Section/B06_Section_UI_Section_Common";
|
||||
import {
|
||||
blocksMinCover,
|
||||
@@ -297,6 +301,28 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
listedBase(storedBase, detail, planted ? irregularStations : null);
|
||||
|
||||
/** 편집 후 다시 그린다 — 길게 누르기(초당 10회)도 한 프레임에 한 번만 그림. */
|
||||
/** 한 본문 [저장] 조각(472-6) — 편집 없음 · 노선 초안 미리보기면 null · 저장 뒤 서버 선형으로 다시 그림. */
|
||||
function alignmentSavePart(): SavePart | null {
|
||||
if (!routeId || !store.dirty() || store === preview) return null; // 노선 초안 미리보기 편집은 안 보냄
|
||||
return {
|
||||
body: { route_id: routeId, ...store.edits() },
|
||||
after: (response) => adoptSaved(response as unknown as ProfileAlignmentSaveResponse),
|
||||
};
|
||||
}
|
||||
|
||||
function adoptSaved(saved: ProfileAlignmentSaveResponse): void {
|
||||
noticeDroppedEdits(saved);
|
||||
const next = saved.profile_alignment as ProfileAlignment | undefined;
|
||||
if (next?.base_pvi?.length) {
|
||||
storedBase = toAlignmentBase(next);
|
||||
base = listed();
|
||||
alignment = buildAlignment(base!, store.edits());
|
||||
if (detail) detail.longitudinal.profile_alignment = next;
|
||||
}
|
||||
store.markSaved();
|
||||
draw();
|
||||
}
|
||||
|
||||
function rebuild(): void {
|
||||
if (base) alignment = buildAlignment(base, store.edits());
|
||||
pagePipeline.pipelineGrade(projectId, store.edits());
|
||||
@@ -597,19 +623,17 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
},
|
||||
/** [확정] 직전에 호출한다. 편집이 없으면 아무 것도 하지 않는다. */
|
||||
async save(): Promise<void> {
|
||||
if (!routeId || !store.dirty() || store === preview) return; // 노선 초안 미리보기 편집은 안 보냄
|
||||
const saved = await saveProfileAlignment(projectId, routeId, store.edits());
|
||||
noticeDroppedEdits(saved);
|
||||
const next = saved.profile_alignment as ProfileAlignment | undefined;
|
||||
if (next?.base_pvi?.length) {
|
||||
storedBase = toAlignmentBase(next);
|
||||
base = listed();
|
||||
alignment = buildAlignment(base!, store.edits());
|
||||
if (detail) detail.longitudinal.profile_alignment = next;
|
||||
}
|
||||
store.markSaved();
|
||||
draw();
|
||||
const part = alignmentSavePart();
|
||||
if (!part) return;
|
||||
await part.after?.(
|
||||
(await saveProfileAlignment(projectId, routeId!, store.edits())) as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>,
|
||||
);
|
||||
},
|
||||
/** 한 본문 [저장] 조각(472-6) — 편집 없음 · 노선 초안 미리보기면 null · 저장 뒤 서버 선형으로 다시 그림. */
|
||||
savePart: () => alignmentSavePart(),
|
||||
clear() {
|
||||
detail = null;
|
||||
base = null;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import { hideLoadingOverlay, showLoadingOverlay, showToast } from "@ui/ui_template_elements";
|
||||
import {
|
||||
flushPendingPipes,
|
||||
pipesPart,
|
||||
readPendingPipes,
|
||||
writePendingPipes,
|
||||
} from "../../B05_Profile/B05_Profile_Api_Pipes_Draft";
|
||||
@@ -34,17 +35,39 @@ import {
|
||||
fetchStructures,
|
||||
flushPendingStructures,
|
||||
readPendingStructures,
|
||||
structuresPart,
|
||||
writePendingStructures,
|
||||
} from "../../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { flushPlusStations, outsideNotice } from "../../B05_Profile/B05_Profile_Api_PlusStations";
|
||||
import { flushRouteParts, saveProfileAlignment } from "../../B05_Profile/B05_Profile_Api_Fetch";
|
||||
import {
|
||||
flushPlusStations,
|
||||
outsideNotice,
|
||||
plusStationsPart,
|
||||
} from "../../B05_Profile/B05_Profile_Api_PlusStations";
|
||||
import {
|
||||
flushRouteParts,
|
||||
routePartsBody,
|
||||
saveProfileAlignment,
|
||||
} from "../../B05_Profile/B05_Profile_Api_Fetch";
|
||||
import {
|
||||
markAlignmentSaved,
|
||||
pendingAlignment,
|
||||
writeAlignmentDraft,
|
||||
} from "../../B05_Profile/B05_Profile_UI_Profile_Edit";
|
||||
import { flushCulvertOptions } from "../../B06_Section/B06_Section_Api_Culvert_Options";
|
||||
import { flushPageSettings } from "../../B05_Profile/B05_Profile_Api_PageSettings";
|
||||
import {
|
||||
culvertOptionsPart,
|
||||
flushCulvertOptions,
|
||||
} from "../../B06_Section/B06_Section_Api_Culvert_Options";
|
||||
import {
|
||||
flushPageSettings,
|
||||
pageSettingsPart,
|
||||
} from "../../B05_Profile/B05_Profile_Api_PageSettings";
|
||||
import {
|
||||
DesignSaveError,
|
||||
postDesignSave,
|
||||
SAVE_PART_KEYS,
|
||||
type SavePart,
|
||||
type SavePartKey,
|
||||
} from "./design_save_api";
|
||||
import { clearState, stateKey } from "../b_page_state";
|
||||
import {
|
||||
carryAfterRouteSave,
|
||||
@@ -75,18 +98,23 @@ export interface DesignSaveContext {
|
||||
/** 저장 앞 준비 — 면적을 모으기 전 전 측점 재계산(B06). */
|
||||
prepare?: () => Promise<void>;
|
||||
/**
|
||||
* 차례마다 **기본 창구 대신** 도는 일. 없으면 기본 창구(세션 초안 → 정본)가 돈다.
|
||||
* `pipes` 만 예외 — 기본(세션 초안)이 먼저 나가고 그 뒤에 돈다(저장분으로 화면만 맞춤 · PUT 없음 · 433-7).
|
||||
* 한 본문 차례 조각을 보기가 고침(472-6) — 받는 값 = 기본 조각(세션 초안 → 본문 · 없으면 null) ·
|
||||
* 돌려주는 조각이 그 차례 본문 · 저장 뒤 정리(보기 화면 맞춤을 덧붙이거나 보기 목록으로 갈음).
|
||||
*/
|
||||
steps?: Partial<Record<Exclude<DesignSaveStep, "cross" | "route">, () => Promise<void>>>;
|
||||
/** 횡단 — 보기마다 본문이 다르다. `confirm` 이면 단계를 닫는 길로. */
|
||||
cross: (confirm: boolean) => Promise<void>;
|
||||
parts?: Partial<Record<SavePartKey, PartOverride>>;
|
||||
/** 횡단 조각 — 보기마다 본문이 다르다(표준 횡단 · 카드 고침 · 유토). */
|
||||
crossPart: (confirm: boolean) => Promise<SavePart | null>;
|
||||
/** 모두 나간 뒤 — 캐시 비우기 · 화면 다시 읽기 · 알림 · 다음 단계. */
|
||||
done?: (confirm: boolean) => Promise<void> | void;
|
||||
/** 멈춘 저장을 알린다. */
|
||||
failed: (error: unknown, confirm: boolean) => void;
|
||||
}
|
||||
|
||||
export type PartOverride = (
|
||||
base: SavePart | null,
|
||||
confirm: boolean,
|
||||
) => Promise<SavePart | null> | SavePart | null;
|
||||
|
||||
const saving = new Set<string>();
|
||||
|
||||
/** 초안을 내보내는 중인가(323-9) — 그동안 바뀜 신호로 낡은 상세 · 빈 초안을 그리지 않음(뒤처리가 한 번 다시). */
|
||||
@@ -241,6 +269,9 @@ export async function saveDesign(
|
||||
const confirm = options.confirm ?? false;
|
||||
const fromRouteId = ctx.routeId();
|
||||
if (fromRouteId === null) return false;
|
||||
// 노선 초안이 없으면 한 본문 한 요청(472-6 6-A · 서버가 전부 아니면 없음) · 있으면 옛 차례(6-B 전)
|
||||
const routeBody = await pendingRouteSave(ctx.projectId);
|
||||
if (!routeBody) return saveOneBody(ctx, confirm, fromRouteId);
|
||||
let routeId = fromRouteId;
|
||||
/** 노선을 새로 저장했으면 그 결과 — 뒤 차례는 새 노선 · 화면 몫(옛 노선 판) 일은 건너뜀. */
|
||||
let routeSaved: Awaited<ReturnType<typeof saveRouteDraft>> | null = null;
|
||||
@@ -253,8 +284,7 @@ export async function saveDesign(
|
||||
for (const step of DESIGN_SAVE_ORDER) {
|
||||
try {
|
||||
if (step === "route") {
|
||||
const body = await pendingRouteSave(ctx.projectId); // 안 바뀌었으면 null — 안 보냄
|
||||
if (!body) continue;
|
||||
const body = routeBody;
|
||||
const [before, beforeStructures] = await Promise.all([
|
||||
savedPipesIfDraft(ctx.projectId),
|
||||
savedStructuresIfDraft(ctx.projectId),
|
||||
@@ -273,27 +303,15 @@ export async function saveDesign(
|
||||
continue;
|
||||
}
|
||||
if (step === "cross") {
|
||||
if (!routeSaved) await ctx.cross(confirm);
|
||||
else {
|
||||
carried = true;
|
||||
const outside = await carryAfterRouteSave(ctx.projectId, fromRouteId, routeId);
|
||||
showToast(routeSaveSummary(routeSaved, outside), "info");
|
||||
}
|
||||
carried = true;
|
||||
const outside = await carryAfterRouteSave(ctx.projectId, fromRouteId, routeId);
|
||||
if (routeSaved) showToast(routeSaveSummary(routeSaved, outside), "info");
|
||||
continue;
|
||||
}
|
||||
// 상단측은 옛 누가거리 열쇠 — 노선이 바뀌면 횡단 차례(`carryAfterRouteSave`)가 새 측점으로 옮긴 뒤 보냄(443-2)
|
||||
if (step === "uphill" && routeSaved) continue;
|
||||
// 노선이 바뀌면 화면 몫 일(옛 노선 판 목록 · 계획고 판)은 건너뛰고 기본 창구만 새 노선으로.
|
||||
const own = routeSaved ? undefined : ctx.steps?.[step];
|
||||
const base = defaultStep(step, ctx.projectId, routeId, fromRouteId, confirm);
|
||||
const run =
|
||||
step === "pipes"
|
||||
? async () => {
|
||||
await base();
|
||||
await own?.();
|
||||
}
|
||||
: (own ?? base);
|
||||
await run();
|
||||
await defaultStep(step, ctx.projectId, routeId, fromRouteId, confirm)();
|
||||
} catch (error) {
|
||||
throw stepFailed(step, error); // 그 차례에서 멈춤 · 이름째 알림(429 ⑨)
|
||||
}
|
||||
@@ -322,3 +340,86 @@ export async function saveDesign(
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
/** 한 본문 기본 조각 — 세션 초안 그대로(옛 기본 창구와 같은 본문). */
|
||||
async function defaultPart(
|
||||
key: Exclude<SavePartKey, "cross">,
|
||||
projectId: string,
|
||||
routeId: number,
|
||||
confirm: boolean,
|
||||
): Promise<SavePart | null> {
|
||||
switch (key) {
|
||||
case "settings":
|
||||
return pageSettingsPart(projectId);
|
||||
case "pipes":
|
||||
return pipesPart(projectId, confirm);
|
||||
case "structures":
|
||||
return structuresPart(projectId);
|
||||
case "plusStations":
|
||||
return plusStationsPart(projectId);
|
||||
case "alignment": {
|
||||
const draft = pendingAlignment(routeId);
|
||||
if (!draft) return null;
|
||||
return {
|
||||
body: { route_id: routeId, ...draft },
|
||||
after: (response) => {
|
||||
if (response) noticeDroppedEdits(response);
|
||||
markAlignmentSaved(routeId);
|
||||
},
|
||||
};
|
||||
}
|
||||
case "culvertOptions":
|
||||
return culvertOptionsPart(stateKey("culvertopt", projectId, routeId));
|
||||
case "uphill":
|
||||
return {
|
||||
body: await routePartsBody(projectId),
|
||||
after: () => clearState("uphill", projectId),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** 서버 실패 → 화면 글 — 차례 이름째 · 모두 그대로 · 되돌림마저 실패면 「복구 확인 필요」. */
|
||||
function oneBodyError(error: unknown): Error {
|
||||
if (!(error instanceof DesignSaveError))
|
||||
return error instanceof Error ? error : new Error(String(error));
|
||||
if (!error.restored) return new Error(`저장 실패 · 복구 확인 필요 — ${error.message}`);
|
||||
const where = error.step ? `${STEP_LABEL[error.step]} 차례에서 ` : "";
|
||||
return new Error(`${where}실패 — 아무것도 저장하지 않았습니다(모두 그대로). ${error.message}`);
|
||||
}
|
||||
|
||||
/** 노선 안 바뀐 [저장] · [확정] — 조각을 모아 서버 한 창구에 한 번(472-6 6-A). 서버가 다 저장한 뒤에만 조각
|
||||
* 정리(초안 비움 · 화면 맞춤) · 실패면 초안 그대로(다음 [저장]에서 다시). */
|
||||
async function saveOneBody(
|
||||
ctx: DesignSaveContext,
|
||||
confirm: boolean,
|
||||
routeId: number,
|
||||
): Promise<boolean> {
|
||||
showLoadingOverlay();
|
||||
saving.add(ctx.projectId);
|
||||
try {
|
||||
await ctx.prepare?.();
|
||||
const parts: Partial<Record<SavePartKey, SavePart | null>> = {};
|
||||
for (const key of SAVE_PART_KEYS) {
|
||||
if (key === "cross") {
|
||||
parts.cross = await ctx.crossPart(confirm);
|
||||
continue;
|
||||
}
|
||||
const base = await defaultPart(key, ctx.projectId, routeId, confirm);
|
||||
const own = ctx.parts?.[key];
|
||||
parts[key] = own ? await own(base, confirm) : base;
|
||||
}
|
||||
const result = await postDesignSave(ctx.projectId, routeId, confirm, parts);
|
||||
if (result.waited) showToast("앞 저장이 끝나기를 기다린 뒤 저장했습니다.", "info");
|
||||
for (const key of SAVE_PART_KEYS) await parts[key]?.after?.(result.steps[key]);
|
||||
clearState("structure-type-cache", ctx.projectId); // 종류별 캐시는 저장 뒤 비움(54-32)
|
||||
saving.delete(ctx.projectId); // 다 나감 — 뒤처리는 새 상세로 그림
|
||||
await ctx.done?.(confirm);
|
||||
return true;
|
||||
} catch (error) {
|
||||
ctx.failed(oneBodyError(error), confirm);
|
||||
return false;
|
||||
} finally {
|
||||
saving.delete(ctx.projectId);
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/* =============================================================================
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,8 @@ import { drainageSide } from "../../B05_Profile/B05_Profile_UI_Profile_SideTool"
|
||||
import { createDesignPanel, type DesignPanel } from "./design_panel";
|
||||
import { createDesignProfile, type DesignProfile } from "./design_profile";
|
||||
import { attachProfilePopout, attachSubPanelPopouts } from "./design_profile_popout";
|
||||
import { saveDesign, type DesignSaveStep } from "./design_save";
|
||||
import { saveDesign, type PartOverride } from "./design_save";
|
||||
import type { SavePart, SavePartKey } from "./design_save_api";
|
||||
import { markDesignClean } from "./design_leave";
|
||||
import { resetDesign } from "./design_reset";
|
||||
import type { DesignView } from "./design_dock";
|
||||
@@ -71,10 +72,10 @@ export interface DesignProfileBinding {
|
||||
export interface DesignViewSave {
|
||||
routeId: () => number | null;
|
||||
prepare?: () => Promise<void>;
|
||||
steps?: Partial<
|
||||
Record<Exclude<DesignSaveStep, "cross" | "pipes" | "alignment">, () => Promise<void>>
|
||||
>;
|
||||
cross?: (confirm: boolean) => Promise<void>;
|
||||
/** 한 본문 차례 조각 고침(472-6) — 구조물 · 상단측(종단 보기) 등. */
|
||||
parts?: Partial<Record<SavePartKey, PartOverride>>;
|
||||
/** 횡단 조각 — 횡단 보기 몫(표준 횡단 · 카드 고침 · 유토). */
|
||||
crossPart?: (confirm: boolean) => Promise<SavePart | null>;
|
||||
/** 저장 뒤 이 보기만의 캐시 비우기 · 다시 읽기(알림 · 다음 단계는 여기서 한다). */
|
||||
done?: (confirm: boolean) => Promise<void> | void;
|
||||
}
|
||||
@@ -399,8 +400,8 @@ export async function saveSharedDesign(shared: DesignPageShared, confirm: boolea
|
||||
await shared.ensureView("cross");
|
||||
const profileSave = shared.bindings.profile?.save;
|
||||
const crossSave = shared.bindings.cross?.save;
|
||||
const crossBody = crossSave?.cross;
|
||||
if (!crossBody) {
|
||||
const crossPart = crossSave?.crossPart;
|
||||
if (!crossPart) {
|
||||
showToast(L(confirm ? "B06_Profile_Confirm_Failed" : "B06_Profile_Save_Failed"), "error");
|
||||
return;
|
||||
}
|
||||
@@ -411,16 +412,26 @@ export async function saveSharedDesign(shared: DesignPageShared, confirm: boolea
|
||||
projectId: shared.projectId,
|
||||
routeId: () => routeIdOf(shared),
|
||||
prepare: crossSave?.prepare,
|
||||
steps: {
|
||||
// 관 정본 PUT 은 세션 초안 한 번(기본 창구) — 배수유역도는 저장분을 다시 받아 화면만 맞춤(433-7 · 판
|
||||
parts: {
|
||||
// 관 본문은 세션 초안(기본 조각) — 배수유역도는 저장 뒤 저장분을 다시 받아 화면만 맞춤(433-7 · 판
|
||||
// 메모리를 다시 보내면 B06 고침 직후 · 판이 안 선 [저장]에서 관을 잃음).
|
||||
...(side ? { pipes: async () => side.reload(confirm) } : {}),
|
||||
// 계획고 초안 — 한 벌 종단도가 저장하고 다시 그린다.
|
||||
...(profile ? { alignment: () => profile.save() } : {}),
|
||||
...profileSave?.steps,
|
||||
...crossSave?.steps,
|
||||
...(side
|
||||
? {
|
||||
pipes: (base: SavePart | null) => ({
|
||||
body: base?.body ?? null,
|
||||
after: async (response: Record<string, unknown> | undefined) => {
|
||||
await base?.after?.(response);
|
||||
await side.reload(confirm);
|
||||
},
|
||||
}),
|
||||
}
|
||||
: {}),
|
||||
// 계획고 초안 — 한 벌 종단도가 본문을 내고 저장 뒤 서버 선형으로 다시 그린다.
|
||||
...(profile ? { alignment: () => profile.savePart() } : {}),
|
||||
...profileSave?.parts,
|
||||
...crossSave?.parts,
|
||||
},
|
||||
cross: crossBody,
|
||||
crossPart,
|
||||
done: async (isConfirm) => {
|
||||
markDesignClean(shared.projectId); // 떠남 확인 — 지금 초안이 저장분(346-4)
|
||||
if (isConfirm) {
|
||||
|
||||
Reference in New Issue
Block a user