sync: 4환경 수렴본 main 반영 (커밋 34건) #10
@@ -1,11 +1,6 @@
|
||||
import { CURRENT_PROJECT_ID_KEY, ROUTES } from "@config/config_frontend";
|
||||
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
||||
import {
|
||||
hideLoadingOverlay,
|
||||
showConfirmDialog,
|
||||
showLoadingOverlay,
|
||||
showToast,
|
||||
} from "@ui/ui_template_elements";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { purgeOtherProjects } from "../A00_Common/b_asset_cache";
|
||||
import { createProgressCircle } from "@ui/ui_template_progress";
|
||||
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
|
||||
@@ -21,13 +16,9 @@ import {
|
||||
type SurfaceModelSummary,
|
||||
} from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import {
|
||||
clearRouteLatestCache,
|
||||
confirmRoute,
|
||||
fetchLatestRoute,
|
||||
readRouteLatestCache,
|
||||
resetRouteDesign,
|
||||
writeRouteLatestCache,
|
||||
solveRoute,
|
||||
updateContourInterval,
|
||||
type RouteLatestResponse,
|
||||
} from "./B05_Profile_Api_Fetch";
|
||||
@@ -43,24 +34,22 @@ import {
|
||||
fetchSectionContext,
|
||||
type SectionDetailResponse,
|
||||
} from "../B06_Section/B06_Section_Api_Fetch";
|
||||
import { flushCulvertOptions } from "../B06_Section/B06_Section_Api_Culvert_Options";
|
||||
import {
|
||||
invalidateSectionDetail,
|
||||
loadSectionDetail,
|
||||
saveCachedCrossPatches,
|
||||
} from "../B06_Section/B06_Section_Section_Store";
|
||||
import { clearStandardCrossSession } from "../B06_Section/B06_Section_UI_Standard_Panel";
|
||||
import { loadSectionDetail } from "../B06_Section/B06_Section_Section_Store";
|
||||
import { migrateLegacyStations } from "./B05_Profile_Api_Structures";
|
||||
import { refreshCorridor, saveCorridorIfDirty } from "./B05_Profile_UI_Corridor";
|
||||
import {
|
||||
resetDesignAction,
|
||||
solveRouteAction,
|
||||
tempSaveAction,
|
||||
type PageActionContext,
|
||||
} from "./B05_Profile_UI_Page_Actions";
|
||||
import "./B05_Profile_UI_Style.css";
|
||||
import "./B05_Profile_UI_Style_Structures.css";
|
||||
import {
|
||||
circlePoint,
|
||||
DEFAULT_ROAD_WIDTHS,
|
||||
fetchRoadWidths,
|
||||
interpolateIrregularStations,
|
||||
restorePoints,
|
||||
routePoint,
|
||||
toBounds,
|
||||
toGradeClass,
|
||||
} from "./B05_Profile_UI_Page_Helpers";
|
||||
@@ -241,14 +230,14 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
}
|
||||
|
||||
const panel = createRoutePanel({
|
||||
onSolve: () => void solve(),
|
||||
onTempSave: () => void tempSave(),
|
||||
onSolve: () => void solveRouteAction(actionContext),
|
||||
onTempSave: () => void tempSaveAction(actionContext),
|
||||
onGoCross: () => {
|
||||
// 페이지 이동 = 코리도 영구저장 시점(2026-08-23 사용자 확정) — 이동은 막지 않는다.
|
||||
if (latest?.route?.id) void saveCorridorIfDirty(activeProjectId, latest.route.id);
|
||||
navigateTo(ROUTES.B06_SECTION);
|
||||
},
|
||||
onReset: () => void resetDesign(),
|
||||
onReset: () => void resetDesignAction(actionContext),
|
||||
onContourApply: (interval) => applyContours(interval),
|
||||
onSurfaceVisible: viewer.setSurfaceVisible,
|
||||
onCorridorVisible: (visible) => {
|
||||
@@ -565,151 +554,22 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function solve(): Promise<void> {
|
||||
if (!confirmedSurface || !latest) return;
|
||||
const points = viewer.markers.getPoints();
|
||||
if (!points.bp || !points.ep) {
|
||||
showToast("BP와 EP를 지형에 배치하세요.", "error");
|
||||
return;
|
||||
}
|
||||
const values = panel.values();
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
const solved = await solveRoute(activeProjectId, {
|
||||
filter_key: latest.surface_params.source_filter,
|
||||
method: latest.surface_params.method,
|
||||
smooth: latest.surface_params.smooth,
|
||||
surface_model_id: confirmedSurface.id,
|
||||
algorithm: values.algorithm,
|
||||
bp: routePoint(points.bp),
|
||||
ep: routePoint(points.ep),
|
||||
cp: points.cp.map(routePoint),
|
||||
ap: points.ap.map(circlePoint),
|
||||
fp: points.fp.map(circlePoint),
|
||||
grade_class: values.gradeClass,
|
||||
paved: values.paved,
|
||||
min_curve_radius_m: values.minCurveRadius,
|
||||
max_uphill_grade: values.maxUphillGrade,
|
||||
max_downhill_grade: values.maxDownhillGrade,
|
||||
min_uphill_grade: values.minUphillGrade,
|
||||
min_downhill_grade: values.minDownhillGrade,
|
||||
allow_avoid_pass_through: values.allowAvoidPassThrough,
|
||||
station_interval_m: values.stationInterval,
|
||||
cross_half_width_m: null,
|
||||
cross_sample_interval_m: values.crossSampleInterval,
|
||||
long_sample_interval_m: values.longSampleInterval,
|
||||
terrain_type: values.terrainType,
|
||||
design_speed_kph: values.designSpeed,
|
||||
max_grade_pct: values.maxGradePct,
|
||||
min_vertical_radius_m: values.minVerticalRadius,
|
||||
min_tangent_length_m: values.minTangentLength,
|
||||
start_elevation_offset_m: values.startElevationOffset,
|
||||
end_elevation_offset_m: values.endElevationOffset,
|
||||
enforce_pipe_clearance: values.enforcePipeClearance,
|
||||
});
|
||||
// 새 경로는 측점 구성이 달라지므로 이전 상단측 변경분을 폐기한다(자동 판정 재사용).
|
||||
uphillOverrides.clear();
|
||||
persistUphillOverrides();
|
||||
renderLatest(await loadLatest(true));
|
||||
await restoreSections(solved.route_id);
|
||||
if (solved.cross_section_count === null) {
|
||||
showToast("경로는 저장되었지만 종횡단 생성에 실패했습니다.", "error");
|
||||
} else if (solved.grade_summary === null) {
|
||||
showToast("경로·종횡단은 저장되었지만 계획선 산출에 실패했습니다.", "error");
|
||||
} else {
|
||||
showToast("최적 경로 계산이 완료되었습니다.", "success");
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : "경로 계산에 실패했습니다.", "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
/** [임시저장] — 현재 편집을 영구저장소에 남기되 워크플로 단계·페이지는 그대로 둔다.
|
||||
* 종·횡 통합 확정은 B06 [확정]이 담당한다(2026-08-08 워크플로우 재정의). */
|
||||
async function tempSave(): Promise<void> {
|
||||
if (!routeReady) return;
|
||||
// 관 매설 지점을 B04와 같은 저장소에 남긴다 — 저장 실패가 임시저장을 막지는 않는다.
|
||||
await profilePanel.drainage.savePipes().catch(() => 0);
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
// 종단 계획선 편집은 화면에서만 계산해 두었으므로 저장 시점에 영속화한다.
|
||||
await profilePanel.save();
|
||||
// 구조물도 조작분이 세션에만 있다(2026-08-29 — CLAUDE.md 5장). 실패는 자체 토스트로
|
||||
// 알리고 세션에 남겨 두므로, 여기서 저장 전체를 멈추지 않는다.
|
||||
await bridge.saveStructuresIfDirty();
|
||||
// 비정규 측점·상단측 변경분까지 데이터로는 확정 저장하되, 단계 완료 전이는 하지 않는다.
|
||||
await confirmRoute(
|
||||
activeProjectId,
|
||||
{
|
||||
filter_key: latest?.surface_params.source_filter,
|
||||
method: latest?.surface_params.method,
|
||||
smooth: latest?.surface_params.smooth,
|
||||
surface_model_id: confirmedSurface?.id,
|
||||
irregular_stations: bridge.irregularStations().map((station) => ({
|
||||
chainage_m: station.chainage_m,
|
||||
structure: station.structure,
|
||||
})),
|
||||
// 상단측(측구 방향) 사용자 변경분 — 종단 정본에 병합되어 B06이 그대로 소비한다.
|
||||
uphill_overrides: [...uphillOverrides.entries()].map(([chainage, side]) => ({
|
||||
chainage_m: Number(chainage),
|
||||
side,
|
||||
})),
|
||||
},
|
||||
false,
|
||||
);
|
||||
// B06 조정창에서 만진 배수관 구간값도 세션에만 있다 — 함께 내보낸다
|
||||
// (CLAUDE.md 5장: 영구저장은 [저장]·[확정]에서만). 실패해도 저장은 진행한다.
|
||||
if (latest?.route?.id != null) {
|
||||
await flushCulvertOptions(
|
||||
activeProjectId,
|
||||
`b06:culvertopt:${activeProjectId}:${latest.route.id}`,
|
||||
).catch(() => undefined);
|
||||
}
|
||||
// B06에서 만져 **캐시에 얹힌** 횡단 수정분을 함께 남긴다 — 안 보내면 바로 아래
|
||||
// 캐시 비우기에서 사라진다. 계획선 저장 **뒤에** 보내야 사용자 수정 1세트가
|
||||
// 최종본으로 얹힌다(2026-08-24 사용자 지적).
|
||||
if (latest?.route?.id != null) {
|
||||
await saveCachedCrossPatches(activeProjectId, latest.route.id);
|
||||
}
|
||||
// 서버가 종단 정본의 계획선을 다시 썼다 — 공유 캐시를 비워 B06이 옛 계획선을 못 보게 한다.
|
||||
invalidateSectionDetail(activeProjectId);
|
||||
renderLatest(await loadLatest(true));
|
||||
// 임시저장 = 코리도 영구저장 시점(2026-08-23) — 실패해도 임시저장은 성공 처리.
|
||||
if (latest?.route?.id) void saveCorridorIfDirty(activeProjectId, latest.route.id);
|
||||
showToast(L("B05_Route_TempSave_Success"), "success");
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : L("B05_Route_TempSave_Failed"), "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
/** [초기화] — 사용자 편집 전부 폐기, 계획노선 CSV 기본값으로 B05·B06 재계산 후 재진입.
|
||||
* 네이티브 confirm은 공용 헤드 브라우저가 자동 취소해 버튼이 죽은 듯 보였다
|
||||
* (2026-08-19 진단) — 화면 안 모달로 확인받는다. */
|
||||
async function resetDesign(): Promise<void> {
|
||||
if (!(await showConfirmDialog(L("B05_Route_Reset_Confirm"), "초기화"))) return;
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
await resetRouteDesign(activeProjectId);
|
||||
// 옛 경로 기준 캐시를 전부 비우고 페이지를 새로 그린다 — 초기 계산본이 정본이 된다.
|
||||
clearRouteLatestCache(activeProjectId);
|
||||
invalidateSectionDetail(activeProjectId);
|
||||
// 프로젝트 단위 세션 값도 함께 버린다 — 키에 route_id가 없어 새 노선에 그대로
|
||||
// 되붙는다(2026-08-28). 초기화는 "사용자 편집을 전부 버린다"가 규약이다.
|
||||
uphillOverrides.clear();
|
||||
persistUphillOverrides();
|
||||
clearStandardCrossSession(activeProjectId);
|
||||
showToast(L("B05_Route_Reset_Success"), "success");
|
||||
navigateTo(ROUTES.B05_PROFILE);
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : L("B05_Route_Reset_Failed"), "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
// 버튼 동작 3종([경로 계산]·[임시저장]·[초기화])은 따로 뗀 모듈이 맡는다(2026-09-02).
|
||||
const actionContext: PageActionContext = {
|
||||
projectId: activeProjectId,
|
||||
latest: () => latest,
|
||||
confirmedSurface: () => confirmedSurface,
|
||||
routeReady: () => routeReady,
|
||||
viewer: () => viewer,
|
||||
panel: () => panel,
|
||||
profilePanel: () => profilePanel,
|
||||
bridge: () => bridge,
|
||||
uphillOverrides,
|
||||
persistUphillOverrides,
|
||||
loadLatest,
|
||||
renderLatest,
|
||||
restoreSections,
|
||||
};
|
||||
|
||||
/* ── 진입 로딩 ─────────────────────────────────────────────────────────
|
||||
* 전부 받아 놓고 한 번에 그리면 몇 초 동안 빈 화면만 보인다. 화면 틀을 먼저 띄우고
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile_UI_Page_Actions.ts
|
||||
* B05 페이지의 **버튼 동작** — [경로 계산]·[임시저장]·[초기화].
|
||||
*
|
||||
* `B05_Profile_UI_Page` 에서 떼어냈다(700줄 제한, 2026-09-02). 화면 조립·로딩 순서는
|
||||
* 페이지에 남고, 여기에는 영구저장소를 건드리는 세 갈래만 둔다(CLAUDE.md 5장:
|
||||
* 세션에 쌓인 조작은 [저장]·[확정]에서만 정본으로 나간다).
|
||||
* ========================================================================== */
|
||||
|
||||
import { ROUTES } from "@config/config_frontend";
|
||||
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
||||
import {
|
||||
hideLoadingOverlay,
|
||||
showConfirmDialog,
|
||||
showLoadingOverlay,
|
||||
showToast,
|
||||
} from "@ui/ui_template_elements";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import type { SurfaceModelSummary } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import {
|
||||
clearRouteLatestCache,
|
||||
confirmRoute,
|
||||
resetRouteDesign,
|
||||
solveRoute,
|
||||
type RouteLatestResponse,
|
||||
} from "./B05_Profile_Api_Fetch";
|
||||
import { flushCulvertOptions } from "../B06_Section/B06_Section_Api_Culvert_Options";
|
||||
import {
|
||||
invalidateSectionDetail,
|
||||
saveCachedCrossPatches,
|
||||
} from "../B06_Section/B06_Section_Section_Store";
|
||||
import { clearStandardCrossSession } from "../B06_Section/B06_Section_UI_Standard_Panel";
|
||||
import { saveCorridorIfDirty } from "./B05_Profile_UI_Corridor";
|
||||
import { circlePoint, routePoint } from "./B05_Profile_UI_Page_Helpers";
|
||||
import type { createRoutePanel } from "./B05_Profile_UI_Panel";
|
||||
import type { createRouteProfilePanel } from "./B05_Profile_UI_Profile_Panel";
|
||||
import type { createStructuresBridge } from "./B05_Profile_UI_Page_Structures";
|
||||
import type { createRouteViewer } from "./B05_Profile_UI_Viewer";
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
|
||||
/** 버튼 동작이 페이지에서 받아 쓰는 창구 — 값은 바뀌므로 함수로 받는다. */
|
||||
export interface PageActionContext {
|
||||
projectId: string;
|
||||
latest: () => RouteLatestResponse | null;
|
||||
confirmedSurface: () => SurfaceModelSummary | null;
|
||||
routeReady: () => boolean;
|
||||
viewer: () => ReturnType<typeof createRouteViewer>;
|
||||
panel: () => ReturnType<typeof createRoutePanel>;
|
||||
profilePanel: () => ReturnType<typeof createRouteProfilePanel>;
|
||||
bridge: () => ReturnType<typeof createStructuresBridge>;
|
||||
/** 상단측(측구 방향) 사용자 변경분 — 키는 누가거리 문자열. */
|
||||
uphillOverrides: Map<string, "left" | "right">;
|
||||
persistUphillOverrides: () => void;
|
||||
loadLatest: (forceFresh?: boolean) => Promise<RouteLatestResponse>;
|
||||
renderLatest: (next: RouteLatestResponse) => void;
|
||||
restoreSections: (routeId: number) => Promise<void>;
|
||||
}
|
||||
|
||||
/** [경로 계산] — 마커·패널 값으로 노선을 풀고 종횡단까지 다시 받는다. */
|
||||
export async function solveRouteAction(ctx: PageActionContext): Promise<void> {
|
||||
const latest = ctx.latest();
|
||||
const confirmedSurface = ctx.confirmedSurface();
|
||||
if (!confirmedSurface || !latest) return;
|
||||
const points = ctx.viewer().markers.getPoints();
|
||||
if (!points.bp || !points.ep) {
|
||||
showToast("BP와 EP를 지형에 배치하세요.", "error");
|
||||
return;
|
||||
}
|
||||
const values = ctx.panel().values();
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
const solved = await solveRoute(ctx.projectId, {
|
||||
filter_key: latest.surface_params.source_filter,
|
||||
method: latest.surface_params.method,
|
||||
smooth: latest.surface_params.smooth,
|
||||
surface_model_id: confirmedSurface.id,
|
||||
algorithm: values.algorithm,
|
||||
bp: routePoint(points.bp),
|
||||
ep: routePoint(points.ep),
|
||||
cp: points.cp.map(routePoint),
|
||||
ap: points.ap.map(circlePoint),
|
||||
fp: points.fp.map(circlePoint),
|
||||
grade_class: values.gradeClass,
|
||||
paved: values.paved,
|
||||
min_curve_radius_m: values.minCurveRadius,
|
||||
max_uphill_grade: values.maxUphillGrade,
|
||||
max_downhill_grade: values.maxDownhillGrade,
|
||||
min_uphill_grade: values.minUphillGrade,
|
||||
min_downhill_grade: values.minDownhillGrade,
|
||||
allow_avoid_pass_through: values.allowAvoidPassThrough,
|
||||
station_interval_m: values.stationInterval,
|
||||
cross_half_width_m: null,
|
||||
cross_sample_interval_m: values.crossSampleInterval,
|
||||
long_sample_interval_m: values.longSampleInterval,
|
||||
terrain_type: values.terrainType,
|
||||
design_speed_kph: values.designSpeed,
|
||||
max_grade_pct: values.maxGradePct,
|
||||
min_vertical_radius_m: values.minVerticalRadius,
|
||||
min_tangent_length_m: values.minTangentLength,
|
||||
start_elevation_offset_m: values.startElevationOffset,
|
||||
end_elevation_offset_m: values.endElevationOffset,
|
||||
enforce_pipe_clearance: values.enforcePipeClearance,
|
||||
});
|
||||
// 새 경로는 측점 구성이 달라지므로 이전 상단측 변경분을 폐기한다(자동 판정 재사용).
|
||||
ctx.uphillOverrides.clear();
|
||||
ctx.persistUphillOverrides();
|
||||
ctx.renderLatest(await ctx.loadLatest(true));
|
||||
await ctx.restoreSections(solved.route_id);
|
||||
if (solved.cross_section_count === null) {
|
||||
showToast("경로는 저장되었지만 종횡단 생성에 실패했습니다.", "error");
|
||||
} else if (solved.grade_summary === null) {
|
||||
showToast("경로·종횡단은 저장되었지만 계획선 산출에 실패했습니다.", "error");
|
||||
} else {
|
||||
showToast("최적 경로 계산이 완료되었습니다.", "success");
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : "경로 계산에 실패했습니다.", "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
/** [임시저장] — 현재 편집을 영구저장소에 남기되 워크플로 단계·페이지는 그대로 둔다.
|
||||
* 종·횡 통합 확정은 B06 [확정]이 담당한다(2026-08-08 워크플로우 재정의). */
|
||||
export async function tempSaveAction(ctx: PageActionContext): Promise<void> {
|
||||
if (!ctx.routeReady()) return;
|
||||
const latest = ctx.latest();
|
||||
const projectId = ctx.projectId;
|
||||
// 관 매설 지점을 B04와 같은 저장소에 남긴다 — 저장 실패가 임시저장을 막지는 않는다.
|
||||
await ctx
|
||||
.profilePanel()
|
||||
.drainage.savePipes()
|
||||
.catch(() => 0);
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
// 종단 계획선 편집은 화면에서만 계산해 두었으므로 저장 시점에 영속화한다.
|
||||
await ctx.profilePanel().save();
|
||||
// 구조물도 조작분이 세션에만 있다(2026-08-29 — CLAUDE.md 5장). 실패는 자체 토스트로
|
||||
// 알리고 세션에 남겨 두므로, 여기서 저장 전체를 멈추지 않는다.
|
||||
await ctx.bridge().saveStructuresIfDirty();
|
||||
// 비정규 측점·상단측 변경분까지 데이터로는 확정 저장하되, 단계 완료 전이는 하지 않는다.
|
||||
await confirmRoute(
|
||||
projectId,
|
||||
{
|
||||
filter_key: latest?.surface_params.source_filter,
|
||||
method: latest?.surface_params.method,
|
||||
smooth: latest?.surface_params.smooth,
|
||||
surface_model_id: ctx.confirmedSurface()?.id,
|
||||
irregular_stations: ctx
|
||||
.bridge()
|
||||
.irregularStations()
|
||||
.map((station) => ({
|
||||
chainage_m: station.chainage_m,
|
||||
structure: station.structure,
|
||||
})),
|
||||
// 상단측(측구 방향) 사용자 변경분 — 종단 정본에 병합되어 B06이 그대로 소비한다.
|
||||
uphill_overrides: [...ctx.uphillOverrides.entries()].map(([chainage, side]) => ({
|
||||
chainage_m: Number(chainage),
|
||||
side,
|
||||
})),
|
||||
},
|
||||
false,
|
||||
);
|
||||
// B06 조정창에서 만진 배수관 구간값도 세션에만 있다 — 함께 내보낸다
|
||||
// (CLAUDE.md 5장: 영구저장은 [저장]·[확정]에서만). 실패해도 저장은 진행한다.
|
||||
if (latest?.route?.id != null) {
|
||||
await flushCulvertOptions(projectId, `b06:culvertopt:${projectId}:${latest.route.id}`).catch(
|
||||
() => undefined,
|
||||
);
|
||||
}
|
||||
// B06에서 만져 **캐시에 얹힌** 횡단 수정분을 함께 남긴다 — 안 보내면 바로 아래
|
||||
// 캐시 비우기에서 사라진다. 계획선 저장 **뒤에** 보내야 사용자 수정 1세트가
|
||||
// 최종본으로 얹힌다(2026-08-24 사용자 지적).
|
||||
if (latest?.route?.id != null) {
|
||||
await saveCachedCrossPatches(projectId, latest.route.id);
|
||||
}
|
||||
// 서버가 종단 정본의 계획선을 다시 썼다 — 공유 캐시를 비워 B06이 옛 계획선을 못 보게 한다.
|
||||
invalidateSectionDetail(projectId);
|
||||
ctx.renderLatest(await ctx.loadLatest(true));
|
||||
// 임시저장 = 코리도 영구저장 시점(2026-08-23) — 실패해도 임시저장은 성공 처리.
|
||||
if (latest?.route?.id) void saveCorridorIfDirty(projectId, latest.route.id);
|
||||
showToast(L("B05_Route_TempSave_Success"), "success");
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : L("B05_Route_TempSave_Failed"), "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
/** [초기화] — 사용자 편집 전부 폐기, 계획노선 CSV 기본값으로 B05·B06 재계산 후 재진입.
|
||||
* 네이티브 confirm은 공용 헤드 브라우저가 자동 취소해 버튼이 죽은 듯 보였다
|
||||
* (2026-08-19 진단) — 화면 안 모달로 확인받는다. */
|
||||
export async function resetDesignAction(ctx: PageActionContext): Promise<void> {
|
||||
if (!(await showConfirmDialog(L("B05_Route_Reset_Confirm"), "초기화"))) return;
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
await resetRouteDesign(ctx.projectId);
|
||||
// 옛 경로 기준 캐시를 전부 비우고 페이지를 새로 그린다 — 초기 계산본이 정본이 된다.
|
||||
clearRouteLatestCache(ctx.projectId);
|
||||
invalidateSectionDetail(ctx.projectId);
|
||||
// 프로젝트 단위 세션 값도 함께 버린다 — 키에 route_id가 없어 새 노선에 그대로
|
||||
// 되붙는다(2026-08-28). 초기화는 "사용자 편집을 전부 버린다"가 규약이다.
|
||||
ctx.uphillOverrides.clear();
|
||||
ctx.persistUphillOverrides();
|
||||
clearStandardCrossSession(ctx.projectId);
|
||||
showToast(L("B05_Route_Reset_Success"), "success");
|
||||
navigateTo(ROUTES.B05_PROFILE);
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : L("B05_Route_Reset_Failed"), "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user