/* ============================================================================= * B01_Dashboard_UI_TempModal.ts * 임시 보관함 — 자료 등록/파일 추가 모달. * * 모달 껍데기는 대시보드의 다른 모달(B01_Dashboard_UI_Modals)과 같은 클래스를 써서 * 생김새를 맞춘다. 파일을 고르면 모달 안에 리스트로 보여 주고, [확인]을 눌러야 * 그룹 생성·업로드가 시작된다(2026-08-08 사용자 지시). * * 크기·확장자 표기 헬퍼도 여기에 둔다 — 보관함 본체(TempUpload)가 이 파일을 가져다 * 쓰므로 import 방향이 한쪽으로만 흐른다. * ========================================================================== */ import { createButton, createInputField, showToast } from "@ui/ui_template_elements"; import { table, text } from "@ui/ui_template_general_blocks"; import { attachModalDismiss, L, type ModalDismissHandle } from "./B01_Dashboard_UI_Common"; /** 파일 확장자 = 보관함 슬롯 종류(csv·shp 세트·las·prj·tfw·tif). */ export function tempFileType(fileName: string): string { const index = fileName.lastIndexOf("."); return index >= 0 ? fileName.slice(index + 1).toLowerCase() : ""; } export function formatTempBytes(bytes: number): string { const gb = bytes / 1024 / 1024 / 1024; if (gb >= 1) return `${gb.toFixed(2)} GB`; const mb = bytes / 1024 / 1024; if (mb >= 1) return `${mb.toFixed(1)} MB`; return `${Math.max(1, Math.round(bytes / 1024))} KB`; } export interface TempModalOptions { title: string; /** 신규 등록이면 true — 그룹 이름을 함께 받는다. 기존 그룹 파일 추가면 false. */ askName: boolean; /** [확인] 눌렀을 때. 모달은 먼저 닫히고, 업로드는 목록에서 진행 상황을 보여 준다. */ onConfirm: (payload: { name: string; files: File[] }) => void; } export function openTempFileModal(options: TempModalOptions): void { const modal = document.createElement("div"); modal.className = "b01-dashboard__modal"; const panel = document.createElement("div"); panel.className = "b01-dashboard__modal-panel"; const heading = document.createElement("h3"); heading.className = "b01-dashboard__modal-title"; heading.textContent = options.title; const nameField = createInputField({ label: L("B01_Temp_Field_Name"), placeholder: L("B01_Temp_Field_Name_Placeholder"), required: true, }); const picker = document.createElement("input"); picker.type = "file"; picker.multiple = true; picker.accept = ".csv,.shp,.shx,.dbf,.cpg,.las,.laz,.tif,.tfw,.prj"; picker.className = "b01-temp__hidden-input"; const pickRow = document.createElement("div"); pickRow.className = "b01-temp__modal-pick"; const caption = document.createElement("span"); caption.textContent = L("B01_Temp_Field_Files"); pickRow.append( caption, createButton({ label: L("B01_Temp_Btn_Pick"), variant: "ghost", onClick: () => picker.click(), }), ); const listHost = document.createElement("div"); listHost.className = "b01-temp__modal-list"; const chosen: File[] = []; function renderList(): void { listHost.replaceChildren( table( [ L("B01_Temp_Table_Type"), L("B01_Temp_Table_Name"), L("B01_Temp_Table_Size"), L("B01_Temp_Table_Action"), ], chosen.map((file) => [ text(tempFileType(file.name).toUpperCase()), text(file.name), text(formatTempBytes(file.size)), createButton({ label: L("Common_Btn_Delete"), variant: "ghost", onClick: () => { const index = chosen.indexOf(file); if (index >= 0) chosen.splice(index, 1); renderList(); }, }), ]), ), ); } picker.addEventListener("change", () => { for (const file of Array.from(picker.files ?? [])) { // 같은 파일을 두 번 고른 경우는 무시한다. const duplicated = chosen.some((item) => item.name === file.name && item.size === file.size); if (!duplicated) chosen.push(file); } picker.value = ""; renderList(); }); const actions = document.createElement("div"); actions.className = "b01-dashboard__actions"; // 닫는 길은 하나로 — [취소]·바깥 클릭·Esc 모두 같은 변경 확인을 거친다 (2026-09-04 사용자 지시). let dismiss: ModalDismissHandle | null = null; actions.append( createButton({ label: L("Common_Btn_Cancel"), variant: "ghost", onClick: () => void dismiss?.tryClose(), }), createButton({ label: L("Common_Btn_Confirm"), onClick: () => { const name = nameField.input.value.trim(); if (options.askName && !name) { showToast(L("B01_Temp_Error_Name"), "error"); return; } if (chosen.length === 0) { showToast(L("B01_Temp_Error_Files"), "error"); return; } modal.remove(); options.onConfirm({ name, files: [...chosen] }); }, }), ); renderList(); panel.append(heading); if (options.askName) panel.append(nameField.root); panel.append(pickRow, listHost, picker, actions); modal.append(panel); document.body.append(modal); // 고른 파일은 입력칸이 아니라 목록에 쌓이므로 따로 견준다. dismiss = attachModalDismiss(modal, panel, { extra: () => chosen.map((file) => `${file.name}:${file.size}`).join(","), }); }