/* ============================================================================= * M02_MasterTemplete_Hopyo_Wizard.ts * 일위대가 불러오기 = 한시 마법사(PLAN 718-1 · 요구서 내역12 4장 22) — 본 칸 한 화면 · 걸음 셋: * ① 결과 층(쓸 수 있는 층만 · 기본 개인) · 결과(일위대가 · 단가산출 = 715-2 뒤 켬) · 엑셀 고르기 → 올림 * (그 층에 둠 · 같은 엑셀 작업 중 = 그 파일 이어서 · 끝낸 같은 엑셀 = 서버 409 안내) · 작업 중 파일 이어서 * ② 보기 · 확인 · 만들기 = 엑셀 보기 창(`Hopyo_Sheet` — [모두 고정으로 들여오기] · 호표 [수정] → * 확인 창 만들기 · [구조물 조합으로] 그대로) · 만들 층 = 고른 층 * ③ [끝내기] = 물음 → 서버가 이번 HP · 격자 · 맞춤셈 캐시 지움 · 만든 일위대가 · 별칭 장부 남음 · 지문 기록 * 작업 중 = 서버에 보관(떠났다 와도 ① 목록에서 이어서) · 옛 HP(지문 없음)는 서버 목록에서 숨김. * ========================================================================== */ import { createButton, el, showConfirmDialog, showToast } from "@ui/ui_template_elements"; import { createCheckbox } from "@ui/ui_template_checkbox"; import { createEmpty } from "@ui/ui_template_empty"; import { createPanelHead } from "@ui/ui_template_panel_head"; import { createSegment } from "@ui/ui_template_segment"; import { createText } from "@ui/ui_template_text"; import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch"; import { deleteHopyo, listHopyo, uploadHopyo, type HopyoFile, type HopyoScope, } from "./M02_MasterTemplete_Hopyo_Api"; import { accessFor, type ManageLayer } from "./M02_MasterTemplete_Layer"; const LAYERS: { layer: ManageLayer; text: string }[] = [ { layer: "personal", text: "개인" }, { layer: "company", text: "회사" }, { layer: "system", text: "마스터" }, ]; const STEPS = [ { value: "1", label: "① 층 · 엑셀" }, { value: "2", label: "② 보기 · 확인 · 만들기" }, { value: "3", label: "③ 끝내기" }, ]; /** 단가산출 결과 — M02 단가산출 서고(715-2)가 들어오면 켬 */ export const SANGEUN_READY = false; const SANGEUN_WHY = "M02 단가산출 칸(715-2)이 들어온 뒤 켬"; type Got = HopyoFile & { 이어서?: boolean }; const why = (error: unknown): string => (error instanceof Error ? error.message : String(error)); const textOf = (layer: string): string => LAYERS.find((x) => x.layer === layer)?.text ?? layer; /** 쓸 수 있는 자기 층(개인 · 회사 · 마스터 차례) */ export async function writableLayers(): Promise { const user = await fetchSessionUser().catch(() => null); return LAYERS.map((x) => x.layer).filter((layer) => accessFor(layer, null, user).canWrite); } export function buildHopyoWizard(): HTMLElement { const steps = createSegment({ options: STEPS.map((s) => ({ ...s, disabled: true })), value: "1", }); steps.root.dataset.hopyoSteps = ""; const body = el("div", { className: "m02-hopyo" }); const root = el("div", { className: "m02-hopyo", attrs: { "data-hopyo-wizard": "" }, children: [ createText( "한시 — 다른 프로그램 일위대가 엑셀을 우리 일위대가로 옮김 · 끝내면 올린 파일 지움 · 별칭 장부는 남음", "hint", ), el("div", { className: "ui-toolbar", children: [steps.root] }), body, ], }); let layers: ManageLayer[] = []; let pick: ManageLayer = "personal"; const scopeOf = (layer: ManageLayer): HopyoScope => ({ layer, owner: null }); async function first(): Promise { steps.set("1"); layers = await writableLayers(); if (!layers.includes(pick)) pick = layers[0] ?? "personal"; const input = el("input", { attrs: { type: "file", accept: ".xls,.xlsx", "data-hopyo-file": "" }, }) as HTMLInputElement; input.hidden = true; const upload = createButton({ label: "엑셀 고르기", variant: "filled", disabled: !layers.length, onClick: () => input.click(), }); upload.title = layers.length ? "설계내역서 엑셀(일위대가 시트)" : "쓸 수 있는 층 없음"; input.addEventListener("change", () => { const file = input.files?.[0]; input.value = ""; if (!file) return; upload.disabled = true; uploadHopyo(scopeOf(pick), file) .then((got: Got) => { if (got.이어서) showToast(`같은 엑셀 작업 중 — ${got.키} 이어서`, "info"); else showToast(`올림 — 호표 ${got.호표수}`, got.어긋남 ? "warning" : "success"); second(pick, got); }) .catch((error) => showToast(why(error), "error")) .finally(() => (upload.disabled = !layers.length)); }); const seg = createSegment({ options: LAYERS.map((x) => ({ value: x.layer, label: x.text, disabled: !layers.includes(x.layer), })), value: pick, onChange: (v) => (pick = v as ManageLayer), }); seg.root.dataset.hopyoLayer = ""; const ilwi = createCheckbox("일위대가(고정 줄)", { checked: true, disabled: true }); const sangeun = createCheckbox("단가산출(산근 블록 · 바탕 없음)", { disabled: !SANGEUN_READY }); sangeun.root.title = SANGEUN_READY ? "" : SANGEUN_WHY; const busy = el("div", { className: "m02-hopyo" }); body.replaceChildren( createText("결과 층", "muted", 14), el("div", { className: "ui-toolbar", children: [seg.root] }), createText("결과", "muted", 14), el("div", { className: "ui-toolbar", children: [ilwi.root, sangeun.root] }), ...(SANGEUN_READY ? [] : [createText(`단가산출 — ${SANGEUN_WHY}`, "hint")]), el("div", { className: "ui-toolbar", children: [upload, input] }), createPanelHead({ title: "작업 중", level: 4, divider: true }).root, busy, ); const rows = ( await Promise.all( layers.map((layer) => listHopyo(scopeOf(layer)) .then((list) => list.map((f) => ({ layer, f }))) .catch(() => []), ), ) ).flat(); busy.replaceChildren( ...(rows.length ? rows.map(({ layer, f }) => { const row = createButton({ label: `${f.파일} · 호표 ${f.호표수} · ${textOf(layer)} — 이어서`, variant: "ghost", onClick: () => second(layer, f), }); row.dataset.hopyoKey = f.키; return row; }) : [createEmpty({ text: "작업 중인 불러오기 없음", small: true })]), ); } function second(layer: ManageLayer, file: HopyoFile): void { steps.set("2"); const scope = scopeOf(layer); const view = createButton({ label: "엑셀 보기 · 확인 · 만들기", variant: "filled", onClick: () => void import("./M02_MasterTemplete_Hopyo_Sheet") .then((m) => m.openHopyoSheet({ scope, key: file.키, 파일: file.파일, target: scope, canWrite: true, onDeleted: () => void first(), }), ) .catch((error: unknown) => showToast(why(error), "error")), }); const finish = createButton({ label: "끝내기", variant: "ghost", onClick: () => void end() }); finish.dataset.hopyoFinish = ""; const back = createButton({ label: "처음으로", variant: "link", onClick: () => void first() }); back.title = "작업은 그대로 보관 — 처음 화면 「작업 중」에서 이어서"; const wrong = file.어긋남 ? ` · 검산 어긋남 ${file.어긋남}` : ""; body.replaceChildren( createText(`${file.파일} · ${textOf(layer)} 층 · 호표 ${file.호표수}${wrong}`, "muted", 14), createText( "엑셀 보기 창에서 [모두 고정으로 들여오기] 또는 호표 [수정] → 확인하고 만들기 · [구조물 조합으로] · 만든 일위대가는 고른 층에 들어감", "hint", ), el("div", { className: "ui-toolbar", children: [view, finish, back] }), ); async function end(): Promise { steps.set("3"); const ok = await showConfirmDialog( `「${file.파일}」 불러오기를 끝냄 — 올린 파일(엑셀 결과 · 격자 · 맞춤 캐시)을 지움 · 만든 일위대가와 별칭 장부는 남음 · 일위대가를 만들었으면 같은 엑셀은 다시 올리지 못함`, "끝내기", ); if (!ok) return steps.set("2"); try { const got = (await deleteHopyo(scope, file.키)) as { 만든?: number }; showToast(`끝냄 — 만든 일위대가 ${got.만든 ?? 0} · 올린 파일 지움`, "success"); } catch (error) { showToast(why(error), "error"); return steps.set("2"); } await first(); } } void first(); return root; }