feat(m01-easycalc): 시작 화면 · 고치는 화면을 Api 목록 모양 · Model EasyDoc 으로 맞춤
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
@@ -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, string | number> = {}): 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<EasyListItem[]> =>
|
||||
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<EasyOpened> =>
|
||||
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<void> => {
|
||||
const openPaper = async (key: string): Promise<void> => {
|
||||
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<string, number>();
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user