Files
Aislo/A00_Common/sheet_formula_cell.ts
T
eomsangdonandClaude Opus 5.5 2e7328d3a5 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>
2026-11-05 18:45:48 +09:00

102 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* A00_Common/sheet_formula_cell.ts
* 줄 식 시트 식 칸 한 벌(PLAN 792 · 794-1) — M02 단가산출 시트 · M01 로직 시트가 같이 씀.
* 칠 때마다 검사(`check` · 150 ms 쉼 · 앞 요청 끊음) → 틀리면 빨간 칸 + 까닭 · 알림 한 줄
* 바꿀 때(Enter · 떠남) — 빈 칸 · 원래 식 그대로 = `put(null)` · 되면 `put(엔진 식)` · 틀리면 안 넣음
* 치는 중 글 = `drafts`(다시 그려도 안 잃음) · 부품 = 입력 · 글 · 서버 검사 길은 부르는 쪽 몫
* ========================================================================== */
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";
const TEXT = {
Formula: ["식", "Formula"],
Hint: ["예) 무게 / 운반량 × (1 + 할증율 / 100)", "e.g. weight / load × 1.2"],
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];
/** 검사 답 — 되나 · 까닭 · 알림(없으면 빈) · 엔진 식 */
export interface FormulaVerdict {
되나: boolean;
까닭: string[];
알림?: string[];
식: string | null;
}
export interface FormulaCellOptions {
/** 시트 칸 열쇠(초점 되찾기 · 치는 중 글) */
key: string;
/** 칸 그릇 이름표 값(`data-sheet-fx`) */
spot: string;
shown: string;
/** 원래 식 글 — 그대로 두면 `put(null)` */
origin: string;
drafts: Map<string, string>;
check: (text: string, signal: AbortSignal) => Promise<FormulaVerdict>;
put: (engine: string | null) => void;
className?: string;
}
export function createFormulaCell(o: FormulaCellOptions): HTMLElement {
const box = createInputField({
value: o.drafts.get(o.key) ?? o.shown,
compact: true,
size: 24,
placeholder: t("Hint"),
});
box.input.setAttribute("aria-label", t("Formula"));
box.input.dataset.sheetCell = o.key;
box.input.spellcheck = false;
const note = createText("", "muted", 12);
note.dataset.sheetCheck = o.key;
let timer = 0;
let ctl: AbortController | null = null;
const run = (text: string): Promise<FormulaVerdict | null> => {
ctl?.abort();
const mine = (ctl = new AbortController());
note.textContent = t("Checking");
return o.check(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;
},
);
};
box.input.addEventListener("input", () => {
o.drafts.set(o.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 === o.origin) {
o.drafts.delete(o.key);
return o.put(null);
}
const got = await run(text);
if (!got?.되나 || !got.식) return void (note.textContent = t("Bad"));
o.drafts.delete(o.key);
o.put(got.식);
});
return el("div", {
className: o.className ?? "m02-sks__fx",
attrs: { "data-sheet-fx": o.spot },
children: [box.root, note],
});
}