diff --git a/M01_MasterData/EasyCalc/M01_EasyCalc_Entry.ts b/M01_MasterData/EasyCalc/M01_EasyCalc_Entry.ts index 3dabd97c8..5b052cb8f 100644 --- a/M01_MasterData/EasyCalc/M01_EasyCalc_Entry.ts +++ b/M01_MasterData/EasyCalc/M01_EasyCalc_Entry.ts @@ -16,12 +16,15 @@ import { showToast, } from "@ui/ui_template_elements"; import { openModal } from "@ui/ui_template_modal"; +import { fetchLogic, fetchLogics, type LogicRow } from "../M01_MasterData_UI_Logic_Api"; import { - fetchLogic, - fetchLogics, - type LogicRow, - type LogicSummary, -} from "../M01_MasterData_UI_Logic_Api"; + listEasy, + openEasy, + plainError, + type EasyListItem, + type EasyOpened, +} from "./M01_EasyCalc_Api"; +import type { EasyDoc } from "./M01_EasyCalc_Model"; import "./M01_EasyCalc_Style.css"; const TEXT = { @@ -79,20 +82,68 @@ const te = (key: TextKey, vars: Record = {}): string => /** 목록에 한 번에 보이는 줄 수 */ const SHOWN = 50; -/** 고치는 화면이 쥔 것 — 2걸음에 Model 의 EasyDoc 으로 바꿈 */ -interface Paper { - key: string | null; - version: string | null; - isBase: boolean; - name: string; - spec: string; - resultUnit: string; - row: LogicRow | null; -} +/* ponytail: Api(sub4) 속이 「준비 중」 인 동안만 Logic_Api 로 대신함 — Api 가 채워지면 아래 셋의 catch 를 지움 */ +const loadList = (): Promise => + listEasy("").catch(() => + fetchLogics().then((all) => + all.map((x) => { + const isBase = !x.키.startsWith("GX"); + const canOpen = x.결과단위.startsWith("원/"); + return { + key: x.키, + name: x.이름, + isBase, + owner: isBase ? te("Base") : te("Mine"), + resultUnit: x.결과단위, + canOpen, + whyNot: canOpen ? null : te("CannotOpen"), + where: `${x.구분} ${x.상세구분}`.trim(), + }; + }), + ), + ); +const loadOne = (key: string): Promise => + openEasy(key).catch(() => + fetchLogic(key).then((one) => ({ + row: one.logic, + version: one.version, + isBase: !one.logic.키.startsWith("GX"), + trial: {}, + })), + ); +const plain = (error: unknown): string => { + try { + return plainError(error); + } catch { + return error instanceof Error ? error.message : String(error); + } +}; -const isMine = (key: string): boolean => key.startsWith("GX"); -/** 돈이 아닌 결과(작업량 등) — 쉬운 길은 「원/…」 만 엶 */ -const canOpen = (item: LogicSummary): boolean => item.결과단위.startsWith("원/"); +/** 빈 종이 — 꼭 채울 칸만(검토 A 「꼭 채울 것」) · 구분 · 상세구분 · 소유 · 키는 서버가 채움 */ +const blankRow = (resultUnit: string): LogicRow => ({ + 키: "", + 원문번호: "", + 이름: "", + 결과단위: resultUnit, + 출처: "자체", + 입력: [], + 중간: [], + 호표: [], + 덧줄: [], + 끝수: null, +}); + +const docOf = (opened: EasyOpened, key: string | null): EasyDoc => ({ + key, + version: opened.version, + isBase: opened.isBase, + name: opened.row.이름, + spec: "", + resultUnit: opened.row.결과단위, + inputs: opened.row.입력 ?? [], + cards: [], + row: opened.row, +}); /** 「원/㎡」 → 「㎡」 */ const unitOf = (resultUnit: string): string => resultUnit.replace(/^원\//, ""); /** 「㎡」 → 「1㎡」 · 「100m」 은 그대로 */ @@ -108,9 +159,9 @@ export function createEasyCalcButton(): HTMLButtonElement { function openEasyCalc(): void { let body: HTMLElement; - let paper: Paper | null = null; + let paper: EasyDoc | null = null; let dirty = false; - let logics: LogicSummary[] | null = null; + let logics: EasyListItem[] | null = null; let query = ""; /** 저장 안 한 것이 있으면 한 번 물음 — 닫아도 되면 true */ @@ -121,26 +172,24 @@ function openEasyCalc(): void { /* ---------- 시작 화면 ---------- */ - const listRow = (item: LogicSummary): HTMLElement => { - const ok = canOpen(item); + const listRow = (item: EasyListItem): HTMLElement => { + const ok = item.canOpen; const row = el("button", { className: "m01-easycalc__item", attrs: { type: "button" }, children: [ - el("span", { className: "m01-easycalc__item-name", text: item.이름 }), - createTag( - isMine(item.키) ? te("Mine") : te("Base"), - isMine(item.키) ? "accent" : "neutral", - ), + el("span", { className: "m01-easycalc__item-name", text: item.name }), + createTag(item.owner, item.isBase ? "neutral" : "accent"), el("span", { className: "m01-easycalc__item-unit", - text: ok ? te("Per", { u: amountOf(item.결과단위) }) : item.결과단위, + text: ok ? te("Per", { u: amountOf(item.resultUnit) }) : item.resultUnit, }), - ...(ok ? [] : [createTag(te("CannotOpen"), "warning")]), + ...(ok ? [] : [createTag(item.whyNot ?? te("CannotOpen"), "warning")]), ], }); row.disabled = !ok; - row.addEventListener("click", () => void openPaper(item)); + row.title = item.where; + row.addEventListener("click", () => void openPaper(item.key)); return row; }; @@ -165,7 +214,7 @@ function openEasyCalc(): void { if (!logics) return; const words = query.trim().toLowerCase().split(/\s+/).filter(Boolean); const hits = words.length - ? logics.filter((x) => words.every((w) => x.이름.toLowerCase().includes(w))) + ? logics.filter((x) => words.every((w) => x.name.toLowerCase().includes(w))) : logics; status.textContent = !hits.length ? te("Start_None") @@ -203,14 +252,12 @@ function openEasyCalc(): void { search.input.focus(); try { - logics ??= await fetchLogics(); + logics ??= await loadList(); } catch (error) { - status.textContent = te("LoadFailed", { - v: error instanceof Error ? error.message : String(error), - }); + status.textContent = te("LoadFailed", { v: plain(error) }); return; } - const mine = logics.filter((x) => isMine(x.키)); + const mine = logics.filter((x) => !x.isBase); mineHost.replaceChildren( ...(mine.length ? mine.map(listRow) @@ -222,34 +269,18 @@ function openEasyCalc(): void { /* ---------- 고치는 화면 ---------- */ const openBlank = (): void => { - paper = { - key: null, - version: null, - isBase: false, - name: "", - spec: "", - resultUnit: unitChoices()[0] ?? "원/㎡", - row: null, - }; + const row = blankRow(unitChoices()[0] ?? "원/㎡"); + paper = { ...docOf({ row, version: "", isBase: false, trial: {} }, null), version: null }; showPaper(); }; - const openPaper = async (item: LogicSummary): Promise => { + const openPaper = async (key: string): Promise => { body.replaceChildren(el("p", { className: "m01-easycalc__muted", text: te("Loading") })); try { - const one = await fetchLogic(item.키); - paper = { - key: one.logic.키, - version: one.version, - isBase: !isMine(one.logic.키), - name: one.logic.이름, - spec: "", - resultUnit: one.logic.결과단위, - row: one.logic, - }; + paper = docOf(await loadOne(key), key); showPaper(); } catch (error) { - showToast(error instanceof Error ? error.message : String(error), "error"); + showToast(plain(error), "error"); void showStart(); } }; @@ -258,7 +289,7 @@ function openEasyCalc(): void { const unitChoices = (): string[] => { const count = new Map(); for (const x of logics ?? []) { - if (canOpen(x)) count.set(x.결과단위, (count.get(x.결과단위) ?? 0) + 1); + if (x.canOpen) count.set(x.resultUnit, (count.get(x.resultUnit) ?? 0) + 1); } return [...count.entries()].sort((a, b) => b[1] - a[1]).map(([u]) => u); }; @@ -327,7 +358,7 @@ function openEasyCalc(): void { : [ undo, createButton({ label: te("Save"), onClick: notYet }), - ...(p.key && isMine(p.key) + ...(p.key && !p.isBase ? [createButton({ label: te("Delete"), variant: "danger", onClick: notYet })] : []), close,