- B01 받은 예정노선 [연결] = 모달(새 임도 프로젝트로 · 기존 프로젝트에 연결) → link API · 409 확인 → 다시 · 끝나면 B03 이동 · 확정(KST) 열 - 연결 new 때 예정노선 설계속도(20/30/40)를 단계 2 params options.design_speed_kph 에 · existing 은 안 건드림 - B03 자동설계 체인이 단계 2 params 의 설계속도를 이어 넘김(없으면 None = 그대로 · 브레인 허락) - 받은 예정노선 목록에 confirmed_at · D04 표 확정 시각 KST 표시(서버 UTC 그대로) - 시험: new 연결 설계속도 기록 · existing · 범위 밖 값 안 넣음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
4.2 KiB
TypeScript
104 lines
4.2 KiB
TypeScript
/* =============================================================================
|
||
* B01_Dashboard_UI_PlanLink.ts
|
||
* 받은 예정노선 [연결] 모달(요구서 345 3-5 · PLAN 531-4) — 본보기 `B03_FileInput_UI_PlanImport.ts`.
|
||
* ① 새 임도 프로젝트로 ② 기존 프로젝트에 연결(우리 회사 ROAD 프로젝트 고르기) →
|
||
* POST /api/dashboard/plan-routes/{assignee}/link · 지울 결과 409 confirm → 확인 창 → 다시 ·
|
||
* 끝나면 그 임도 프로젝트 B03 으로 이동.
|
||
* ========================================================================== */
|
||
|
||
import { API_BASE_URL, ROUTES } from "@config/config_frontend";
|
||
import {
|
||
createButton,
|
||
createSelectField,
|
||
el,
|
||
hideLoadingOverlay,
|
||
showConfirmDialog,
|
||
showLoadingOverlay,
|
||
showToast,
|
||
} from "@ui/ui_template_elements";
|
||
import { openModal } from "@ui/ui_template_modal";
|
||
import { goToWorkflowStage } from "../A00_Common/b_workflow_nav";
|
||
import { fetchUserProjects } from "./B01_Dashboard_Api_Fetch";
|
||
import { L } from "./B01_Dashboard_UI_Common";
|
||
|
||
type LinkTarget = { mode: "new" } | { mode: "existing"; project_id: string };
|
||
|
||
/** 연결 — 지울 결과가 있으면 한 번 묻고 다시 · 성공 = 임도 프로젝트 id */
|
||
async function postLink(assigneeId: number, target: LinkTarget): Promise<string | null> {
|
||
// 「LAS 없이 설계」는 B03 화면 기억값(localStorage) — 기존 프로젝트만
|
||
const lasFree =
|
||
target.mode === "existing" && localStorage.getItem(`b03_las_free_${target.project_id}`) === "1";
|
||
const send = (confirmReplace: boolean): Promise<Response> =>
|
||
fetch(`${API_BASE_URL}/dashboard/plan-routes/${assigneeId}/link`, {
|
||
method: "POST",
|
||
credentials: "include",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ ...target, confirm_replace: confirmReplace, las_free: lasFree }),
|
||
});
|
||
showLoadingOverlay();
|
||
try {
|
||
let response = await send(false);
|
||
let data = (await response.json().catch(() => ({}))) as {
|
||
confirm?: string;
|
||
message?: string;
|
||
detail?: unknown;
|
||
project_id?: string;
|
||
};
|
||
if (response.status === 409 && data.confirm === "replace_outputs") {
|
||
hideLoadingOverlay();
|
||
const agreed = await showConfirmDialog(data.message ?? "", L("DE_Assigned_Link"));
|
||
showLoadingOverlay();
|
||
if (!agreed) return null;
|
||
response = await send(true);
|
||
data = (await response.json().catch(() => ({}))) as typeof data;
|
||
}
|
||
if (!response.ok || !data.project_id) {
|
||
throw new Error(typeof data.detail === "string" ? data.detail : `HTTP ${response.status}`);
|
||
}
|
||
showToast(L("D04_Import_Done"), "success");
|
||
return data.project_id;
|
||
} catch (error) {
|
||
showToast(error instanceof Error ? error.message : String(error), "error");
|
||
return null;
|
||
} finally {
|
||
hideLoadingOverlay();
|
||
}
|
||
}
|
||
|
||
export async function openPlanLinkModal(assigneeId: number, title: string): Promise<void> {
|
||
// 사용자 프로젝트 목록 = 회사 프로젝트(서버 범위) — 그중 임도만
|
||
const roads = (await fetchUserProjects().catch(() => [])).filter(
|
||
(project) => (project.project_kind ?? "ROAD") === "ROAD",
|
||
);
|
||
const run = async (target: LinkTarget, close: () => void): Promise<void> => {
|
||
close();
|
||
const projectId = await postLink(assigneeId, target);
|
||
if (projectId) await goToWorkflowStage(projectId, ROUTES.B03_FILE_INPUT);
|
||
};
|
||
openModal({
|
||
title: `${L("DE_Link_Title")} — ${title}`,
|
||
closeLabel: "×",
|
||
mount: (body, close) => {
|
||
const pick = createSelectField({
|
||
label: L("DE_Link_Existing"),
|
||
options: roads.map((project) => ({ value: project.id, text: project.name })),
|
||
});
|
||
const existing = createButton({
|
||
label: L("DE_Link_Existing"),
|
||
variant: "ghost",
|
||
onClick: () => void run({ mode: "existing", project_id: pick.select.value }, close),
|
||
});
|
||
existing.disabled = !roads.length;
|
||
body.append(
|
||
createButton({
|
||
label: L("DE_Link_New"),
|
||
variant: "filled",
|
||
onClick: () => void run({ mode: "new" }, close),
|
||
}),
|
||
roads.length ? pick.root : el("p", { text: L("DE_Link_NoRoad") }),
|
||
existing,
|
||
);
|
||
},
|
||
});
|
||
}
|