Files
Aislo/B01_Dashboard/B01_Dashboard_UI_PlanLink.ts
T
eomsangdonandClaude Opus 5.5 22e3797ab8 feat(531-4): 대시보드 받은 예정노선 [연결] 모달 · 새 임도 설계속도 → 단계 2 params · 확정 시각 KST
- 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>
2026-10-25 20:55:45 +09:00

104 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* 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,
);
},
});
}