feat(M01): 새로 만들기 모달에 새 길 잇기 — 절 목록으로 원문 절 고르기 · 절로 표 거르기 · 표 조건 값 목록 · 초안 시험 계산 · 자체 로직 저장(GX) · 본떠 만들기

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
2026-09-21 09:43:19 +09:00
co-authored by Claude Sonnet 5
parent 63832239d4
commit beea3fac46
11 changed files with 383 additions and 202 deletions
@@ -7,17 +7,10 @@
* ========================================================================== */
import { createButton, el, showToast } from "@ui/ui_template_elements";
import {
ApiError,
fetchLogicFiles,
runCalc,
saveFiles,
type CalcAnswer,
type LogicFile,
} from "./M01_MasterData_UI_Logic_Api";
import { ApiError, runCalc, type CalcAnswer } from "./M01_MasterData_UI_Logic_Api";
import { formatNumber } from "./M01_MasterData_UI_Logic_Edit";
import { select, textBox } from "./M01_MasterData_UI_Logic_Wizard_Atom";
import { searchLogics } from "./M01_MasterData_UI_Logic_Wizard_Api";
import { createLogic } from "./M01_MasterData_UI_Logic_Wizard_Api";
import {
atomPlain,
buildRow,
@@ -44,7 +37,6 @@ const fail = (e: unknown): void =>
/** 「새로 만들기」 모달을 엶 */
export function openLogicWizard(options: WizardOptions = {}): void {
const d: Draft = emptyDraft();
let files: LogicFile[] = [];
let at = 0;
let answered = false;
const testValues: Record<string, string> = {};
@@ -85,12 +77,17 @@ export function openLogicWizard(options: WizardOptions = {}): void {
next.disabled = !steps[at].ready(d);
};
const onCopied = (key: string): void => {
close();
options.onSaved?.(key);
};
const go = (to: number): void => {
at = Math.max(0, Math.min(steps.length - 1, to));
if (at === STEPS.length) answered = false; // 시험 계산 걸음에 들어올 때마다 새로
const step = steps[at];
head.textContent = tw("Step", { n: at + 1, total: steps.length, name: tw(step.key) });
body.replaceChildren(step.render({ d, changed }));
body.replaceChildren(step.render({ d, changed, onCopied }));
back.disabled = at === 0;
next.hidden = at === steps.length - 1;
changed();
@@ -139,8 +136,6 @@ export function openLogicWizard(options: WizardOptions = {}): void {
};
const calc = async (): Promise<CalcAnswer> => {
if (!files.length) files = await fetchLogicFiles();
const file = (files.find((f) => f.book === d.book) ?? files[0]).file;
const given: Record<string, unknown> = {};
for (const s of collectInputs(d)) {
const raw = (testValues[s.name] ?? "").trim();
@@ -148,56 +143,33 @@ export function openLogicWizard(options: WizardOptions = {}): void {
const option = s.choices?.find((c) => String(c) === raw);
given[s.name] = option ?? (Number.isNaN(Number(raw)) ? raw : Number(raw));
}
return runCalc({ key: "", inputs: given, row: buildRow(d), file });
return runCalc({ key: "", inputs: given, row: buildRow(d) });
};
/* ── 저장 ── */
const saveView = (): HTMLElement => {
const own = files.filter((f) => f.file.includes("자체"));
const message = el("p", { className: "m01w__muted", text: tw("Save_Owner") });
const errors = el("div", { className: "m01w__reasons", attrs: { hidden: "" } });
let file = own[0]?.file ?? "";
const save = createButton({
label: tw("Save_Do"),
disabled: !own.length,
onClick: () => void doSave(file, errors),
});
return el("div", {
className: "m01w__page",
children: [
el("h3", { text: tw("Q_Save") }),
message,
own.length
? select(
own.map((f) => ({ value: f.file, text: f.file })),
file,
(v) => (file = v),
tw("Save_File"),
)
: el("p", { className: "m01w__bad", text: tw("Save_None") }),
el("p", { className: "m01w__muted", text: tw("Save_Owner") }),
errors,
save,
createButton({ label: tw("Save_Do"), onClick: () => void doSave(errors) }),
],
});
};
const doSave = async (file: string, errors: HTMLElement): Promise<void> => {
const target = files.find((f) => f.file === file);
if (!target) return;
const doSave = async (errors: HTMLElement): Promise<void> => {
errors.hidden = true;
try {
const row = buildRow(d);
await saveFiles([
{ file: target.file, version: target.version, changes: [{ op: "add", row }] },
]);
const made = await createLogic(buildRow(d));
showToast(tw("Save_Done"), "success");
const found = await searchLogics(row.);
const mine = found.logics.filter((l) => l. === row.).map((l) => l.);
close();
if (mine.length) options.onSaved?.(mine[mine.length - 1]);
options.onSaved?.(made.key);
} catch (e) {
if (e instanceof ApiError && typeof e.detail === "object" && e.detail !== null) {
const list = (e.detail as { errors?: string[]; stale?: string[] }).errors ?? [];
const list = (e.detail as { errors?: string[] }).errors ?? [];
errors.replaceChildren(...list.map((t) => el("div", { text: t })));
errors.hidden = false;
} else fail(e);
@@ -259,9 +231,6 @@ export function openLogicWizard(options: WizardOptions = {}): void {
};
document.body.append(backdrop);
void fetchLogicFiles()
.then((f) => (files = f))
.catch(fail);
go(0);
}