/* ============================================================================= * B08_Quantity_UI_StructureSheet_Stmate.ts * 라이브러리 칸 — [STmate 엑셀에서 뽑기] (PLAN 4장 · 2026-09-14 브레인 판정). * * 두 걸음: [읽기] → 호표 수·구성 줄 수·못 읽은 사유를 보임(아무것도 안 씀) → 호표를 고르고 * [내 라이브러리에 넣기] → 수를 다시 묻고 **파일을 다시 보냄**(서버가 다시 읽어 씀 — 화면은 줄을 안 보냄). * ⚠ 종류는 지금 보고 있는 장의 종류 — 이름으로 자동으로 안 붙임(판정 Ⓒ). * ⚠ 모양은 옆 제원 칸(`b08-spec*`) 클래스를 그대로 씀. * ========================================================================== */ import { API_BASE_URL } from "@config/config_frontend"; interface ReadHopyo { no: number; name: string; spec: string; unit: string; rows: number; percent_rows: number; contract_rows: number; } interface ReadResult { project: string; hopyo: ReadHopyo[]; counts: { hopyo: number; rows: number }; problems: string[]; } async function postForm(url: string, form: FormData): Promise { const response = await fetch(url, { method: "POST", credentials: "include", body: form }); const payload = (await response.json().catch(() => ({}))) as T & { message?: string }; if (!response.ok) throw new Error(payload.message ?? `HTTP ${response.status}`); return payload; } export function buildStmatePanel(options: { projectId: string; typeId: string; /** 넣은 뒤 — 목록을 다시 받게 함. */ onSaved: () => void; }): HTMLElement { const { projectId, typeId, onSaved } = options; const base = `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/structure-sheets/library`; const box = document.createElement("div"); box.className = "b08-sheet__actions"; const input = document.createElement("input"); input.type = "file"; input.accept = ".xlsx"; input.className = "b08-spec__input"; input.title = "STmate 가 내보낸 내역 엑셀(일위대가표 시트)"; const read = document.createElement("button"); read.type = "button"; read.className = "b08-quantity__tab"; read.textContent = "STmate 엑셀 읽기"; const list = document.createElement("select"); list.className = "b08-spec__input"; list.hidden = true; const put = document.createElement("button"); put.type = "button"; put.className = "b08-spec__save"; put.textContent = "내 라이브러리에 넣기"; put.hidden = true; const status = document.createElement("p"); status.className = "b08-spec__scope"; let result: ReadResult | null = null; input.addEventListener("change", () => { result = null; list.hidden = put.hidden = true; status.textContent = ""; }); read.addEventListener("click", () => { const file = input.files?.[0]; if (!file) { status.textContent = "엑셀 파일을 먼저 고르세요"; return; } void (async () => { read.disabled = true; status.textContent = "읽는 중…"; try { const form = new FormData(); form.append("file", file); result = await postForm(`${base}/stmate/read`, form); list.replaceChildren( ...result.hopyo.map((h) => { const option = document.createElement("option"); option.value = String(h.no); const extra = h.percent_rows ? ` · 가산 행 ${h.percent_rows}` : ""; option.textContent = `제${h.no}호표 ${h.name} ${h.spec} / ${h.unit} · 구성 ${h.rows}줄${extra}`; return option; }), ); list.hidden = put.hidden = result.hopyo.length === 0; const missed = result.problems.length ? ` · 못 읽은 것 ${result.problems.length}건 — ${result.problems.slice(0, 3).join(" / ")}` : ""; status.textContent = `「${result.project}」 호표 ${result.counts.hopyo}개 · 구성 줄 ${result.counts.rows}줄 읽음${missed}`; } catch (error) { status.textContent = error instanceof Error ? error.message : "읽지 못함"; } finally { read.disabled = false; } })(); }); put.addEventListener("click", () => { const file = input.files?.[0]; const picked = result?.hopyo.find((h) => String(h.no) === list.value); if (!file || !result || !picked) return; const question = `읽은 것: 호표 ${result.counts.hopyo}개 · 구성 줄 ${result.counts.rows}줄\n` + `넣을 것: 「${picked.name} ${picked.spec}」 구성 ${picked.rows}줄 → 이 장 종류로 내 라이브러리\n` + "· 같은 종류 내 것이 있으면 덮어씀\n· 수량은 원문 시점값(현행 품셈 대조 전) · 단가는 안 가져옴"; if (!window.confirm(question)) return; void (async () => { put.disabled = true; status.textContent = "넣는 중…"; try { const form = new FormData(); form.append("file", file); form.append("hopyo_no", String(picked.no)); form.append("type_id", typeId); const saved = await postForm<{ code: string; name: string; note: string }>( `${base}/stmate/save`, form, ); status.textContent = `내 라이브러리에 넣음 — 「${saved.name}」 [고정형] · ${saved.note}`; onSaved(); } catch (error) { status.textContent = error instanceof Error ? error.message : "넣지 못함"; } finally { put.disabled = false; } })(); }); const wrap = document.createElement("div"); box.append(read, put); wrap.append(input, box, list, status); return wrap; }