feat(794-1): M01 관리자 줄 식 시트 — 로직 상세 첫 보기 「시트」 · 식 칸 칠 때마다 검사 · 기존 [저장] 길

- 로직 상세 보기 첫 칸 「시트」(기본) — 이름 | 식 | 값 | 비목 · 근거 · 옵션(시험 값) · 변수 · 계산 · 자원 · 율 · 덧줄 · 단계 · 끝수 · 소계 · 합계 · 상태줄 지금 · 고치기 전
- 식 글 = 이름 꼴(M02 단가산출 시트와 같은 번역 · 새 서버 M01_MasterData_Sheet.py · POST /api/m01/logic/sheet · /logic/sheet/check · 저장 때와 같은 엔진 검사 · 쓰기 없음)
- 식 고침 = 시스템 관리자만 · 되는 식만 줄 사본에 · 캐시 → 기존 [저장](개정 확인 · 서버 검사 422) · [원래로]
- 식 칸 몸 = 공용 A00_Common/sheet_formula_cell.ts — M02 단가산출 시트도 같은 것을 씀(동작 그대로)
- 자체 로직(GX) 편집기 식 날 입력칸 → 읽기만(시트 식 칸으로 갈음)
- 시험: test_794_1_m01_sheet 새로 · test_790_1 식 칸 문구 자리를 공용 파일로 옮김(식 칸 몸이 공용으로 감) · test_717_2b · test_722 LogicLab.ts 줄 상한 627 → 637(시트 이음 10 줄 · 셈 · 고침은 시트 파일로 뺌 · 700 아래)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 18:45:48 +09:00
co-authored by Claude Opus 5.5
parent b989fe1724
commit 2e7328d3a5
17 changed files with 970 additions and 79 deletions
@@ -14,7 +14,7 @@ import { createCalcSheetRow } from "@ui/ui_template_calc_row";
import { el } from "@ui/ui_template_elements";
import { createInputField } from "@ui/ui_template_input";
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { createText } from "@ui/ui_template_text";
import { createFormulaCell } from "../A00_Common/sheet_formula_cell";
import { call, type Layer } from "./M02_MasterTemplete_Api_Fetch";
import type { EditCtx } from "./M02_MasterTemplete_Sangeun_SheetEdit";
import {
@@ -28,16 +28,12 @@ import {
import { num, type SpotInfo } from "./logic/M02_Sangeun_Sheet_Model";
const TEXT = {
Formula: ["식", "Formula"],
Edit: ["식 고침", "Edit formula"],
Close: ["식 닫기", "Close formula"],
Hint: ["예) 무게 / 운반량 × (1 + 할증율 / 100)", "e.g. weight / load × 1.2"],
Var: ["변수", "Variable"],
VarName: ["변수 이름", "Variable name"],
AddVar: ["+ 변수 줄", "+ Variable"],
DropVar: ["변수 빼기", "Remove variable"],
Bad: ["식이 틀려 안 넣음", "Invalid formula — not applied"],
Checking: ["검사 중", "Checking…"],
} as const satisfies Record<string, readonly [string, string]>;
const t = (k: keyof typeof TEXT): string => TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0];
@@ -97,7 +93,7 @@ export function clearNums<T extends FormulaDoc>(doc: T, spot: string): T {
return out as T;
}
/** 식 칸 — 칠 때마다 검사(앞 요청 끊음) · 바꿀 때 넣음 · `put(엔진 식 | null)` */
/** 식 칸 — 칠 때마다 검사(앞 요청 끊음) · 바꿀 때 넣음 · `put(엔진 식 | null)` · 몸 = 공용 `sheet_formula_cell` */
function formulaBox(
c: FormulaCtx,
spot: string,
@@ -106,63 +102,14 @@ function formulaBox(
origin: string,
put: (engine: string | null) => void,
): HTMLElement {
const box = createInputField({
value: c.drafts.get(key) ?? shown,
compact: true,
size: 24,
placeholder: t("Hint"),
});
box.input.setAttribute("aria-label", t("Formula"));
box.input.dataset.sheetCell = key;
box.input.spellcheck = false;
const note = createText("", "muted", 12);
note.dataset.sheetCheck = key;
let timer = 0;
let ctl: AbortController | null = null;
let last: Promise<CheckAnswer | null> | null = null;
const run = (text: string): Promise<CheckAnswer | null> => {
ctl?.abort();
const mine = (ctl = new AbortController());
note.textContent = t("Checking");
last = checkFormula(c, spot, text, mine.signal).then(
(got) => {
if (mine !== ctl) return got;
box.setError(got.되나 ? undefined : got.까닭.join(" · "));
note.textContent = got.알림.join(" · ");
return got;
},
(error: unknown) => {
if (mine !== ctl || mine.signal.aborted) return null;
box.setError(error instanceof Error ? error.message : String(error));
note.textContent = "";
return null;
},
);
return last;
};
box.input.addEventListener("input", () => {
c.drafts.set(key, box.input.value);
window.clearTimeout(timer);
const text = box.input.value.trim();
if (!text) return void (ctl?.abort(), box.setError(undefined), (note.textContent = ""));
timer = window.setTimeout(() => void run(text), 150);
});
box.input.addEventListener("change", async () => {
window.clearTimeout(timer);
const text = box.input.value.trim();
if (!text || text === origin) {
c.drafts.delete(key);
return put(null);
}
const got = await run(text);
if (!got?.되나) return void (note.textContent = t("Bad"));
c.drafts.delete(key);
put(got.식);
});
return el("div", {
className: "m02-sks__fx",
attrs: { "data-sheet-fx": spot },
children: [box.root, note],
return createFormulaCell({
key,
spot,
shown,
origin,
drafts: c.drafts,
check: (text, signal) => checkFormula(c, spot, text, signal),
put,
});
}