/* ============================================================================= * B03_FileInput_UI_PlanImport.ts * 예정노선에서 가져오기(요구서 345 3-5 · PLAN 531-3) — 본보기 `B03_FileInput_UI_TempPicker.ts`. * 노선 칸 옆 [예정노선에서 가져오기] = 우리 회사에 지정된 확정 연도 고르기 → * POST /api/dashboard/plan-routes/{assignee}/link {mode: existing, project_id: 이 프로젝트}. * 머리 띠 = 이어 받은 예정노선이 바뀌면 「예정노선이 바뀜 — [다시 가져오기]」(같은 길 · 자동 반영 없음). * 옛 결과가 지워지면 서버가 409 confirm 으로 묻고 → 확인 창 → confirm_replace 로 다시. * ========================================================================== */ import { API_BASE_URL, CURRENT_PROJECT_ID_KEY } from "@config/config_frontend"; import { createButton, el, hideLoadingOverlay, showConfirmDialog, showLoadingOverlay, showToast, } from "@ui/ui_template_elements"; import { t } from "@ui/ui_template_locale"; import { openModal } from "@ui/ui_template_modal"; interface AssignedRoute { assignee_id: number; project_name: string; year: number; linked_project_id: string | null; linked_project_name: string | null; update_available: boolean; } interface PlanSource { linked: boolean; assignee_id?: number; plan_name?: string; year?: number; update_available?: boolean; } async function getJson(path: string): Promise { const response = await fetch(`${API_BASE_URL}${path}`, { credentials: "include" }); if (!response.ok) throw new Error(`HTTP ${response.status}`); return (await response.json()) as T; } /** 연결 — 지울 결과가 있으면 한 번 묻고 다시 · 성공 = 참 */ async function linkRoute( assigneeId: number, projectId: string, lasFree: boolean, ): Promise { const send = (confirmReplace: boolean): Promise => fetch(`${API_BASE_URL}/dashboard/plan-routes/${assigneeId}/link`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode: "existing", project_id: projectId, 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; }; if (response.status === 409 && data.confirm === "replace_outputs") { hideLoadingOverlay(); const agreed = await showConfirmDialog(data.message ?? "", t("D04_Import_Btn")); showLoadingOverlay(); if (!agreed) return false; response = await send(true); data = (await response.json().catch(() => ({}))) as typeof data; } if (!response.ok) { throw new Error(typeof data.detail === "string" ? data.detail : `HTTP ${response.status}`); } showToast(t("D04_Import_Done"), "success"); return true; } catch (error) { showToast(error instanceof Error ? error.message : String(error), "error"); return false; } finally { hideLoadingOverlay(); } } /** 노선 칸에 단추 · 본문 위에 바뀜 띠를 끼움 — `onImported` = 카드 현황 다시 읽기. * 프로젝트 · 「LAS 없이 설계」는 B03 화면과 같은 기억값(localStorage)을 읽음. */ export function mountPlanImport( buttonHost: HTMLElement, bannerHost: HTMLElement, onImported: () => void, ): void { const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY) ?? ""; if (!projectId) return; const lasFree = (): boolean => localStorage.getItem(`b03_las_free_${projectId}`) === "1"; const banner = el("div", { className: "b03-file__overview-pending b03-file__plan-update" }); banner.hidden = true; bannerHost.prepend(banner); const done = async (assigneeId: number): Promise => { if (!(await linkRoute(assigneeId, projectId, lasFree()))) return; onImported(); void refreshBanner(); }; async function refreshBanner(): Promise { const source = await getJson(`/projects/${projectId}/plan-source`).catch( () => null, ); banner.hidden = !source?.update_available; if (!source?.update_available || source.assignee_id === undefined) return; const assigneeId = source.assignee_id; banner.replaceChildren( el("span", { text: `${t("D04_Update_Banner")} (${source.plan_name} ${source.year}) — ` }), createButton({ label: t("D04_Update_Reimport"), variant: "ghost", size: "sm", onClick: () => void done(assigneeId), }), ); } const openPicker = async (): Promise => { let routes: AssignedRoute[]; try { routes = (await getJson<{ routes: AssignedRoute[] }>("/dashboard/plan-routes/assigned")) .routes; } catch (error) { showToast(error instanceof Error ? error.message : String(error), "error"); return; } const { close } = openModal({ title: t("D04_Import_Title"), closeLabel: "×", mount: (body) => { if (!routes.length) { body.append(el("p", { text: t("D04_Import_Empty") })); return; } for (const route of routes) { const linked = route.linked_project_name ? ` · ${t("D04_Linked")}: ${route.linked_project_name}` : ""; body.append( el("div", { className: "b03-file__plan-pick", children: [ el("span", { text: `${route.project_name} · ${route.year}${linked}` }), createButton({ label: t("D04_Import_Btn"), variant: "filled", size: "sm", onClick: () => { close(); void done(route.assignee_id); }, }), ], }), ); } }, }); }; buttonHost.append( createButton({ label: t("D04_Import_Btn"), variant: "ghost", onClick: () => void openPicker(), }), ); void refreshBanner(); }