refactor(m02-df): 입력 판 사용자 요소 만들기 지움(시험판 내용은 임시) — 요소는 마스터 요소 화면에서

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
This commit is contained in:
2026-10-02 07:56:57 +09:00
co-authored by Claude Sonnet 5.5
parent fd2a6851c7
commit 33700dc940
3 changed files with 10 additions and 129 deletions
@@ -7,20 +7,14 @@
* ========================================================================== */
import { currentLanguageIndex } from "@ui/ui_template_locale";
import {
createButton,
createInputField,
createSelectField,
el,
showToast,
} from "@ui/ui_template_elements";
import { createButton, createInputField, createSelectField, el } from "@ui/ui_template_elements";
import {
fetchFiles,
fetchRows,
fetchSubs,
type SubInfo,
} from "../../M01_MasterData/M01_MasterData_Api_Fetch";
import { cfPlain, cfUserElementAdd, cfUserElements, cfValues } from "./M02_DF_Api";
import { cfPlain, cfValues } from "./M02_DF_Api";
import type { CfBox, CfGroup, CfLogic } from "./M02_DF_Model";
import {
GROUPS,
@@ -40,7 +34,6 @@ import {
} from "./M02_DF_InputPanel_Build";
import { renderLogicInput } from "./M02_DF_InputPanel_Logic";
import { renderGroupRule } from "./M02_DF_InputPanel_Rule";
import { NO_UNIT, createUnitPick } from "./M02_DF_UnitPick";
import { renderMaterial } from "./M02_DF_InputPanel_Material";
import { renderTablePick } from "./M02_DF_InputPanel_Table";
import { renderVariables } from "./M02_DF_InputPanel_Vars";
@@ -52,17 +45,7 @@ interface Answer {
ok: boolean;
value?: number | string | null;
}
interface UserEl {
id: string;
group: string;
name: string;
spec: string;
unit: string;
value: number | null;
}
const apiValues = cfValues as unknown as (items: object[], users?: object[]) => Promise<Answer[]>;
const apiUsers = cfUserElements as unknown as () => Promise<UserEl[]>;
const apiAdd = cfUserElementAdd as unknown as (e: Omit<UserEl, "id">) => Promise<UserEl>;
const TEXT = {
Kind: ["무엇을 넣을까요?", "What goes in?"],
@@ -94,15 +77,6 @@ const TEXT = {
Loading: ["찾는 중…", "Searching…"],
NoRow: ["찾는 요소 없음", "Nothing found"],
Count: ["{n} 개 중 앞 {m} 개", "First {m} of {n}"],
Mine: ["내가 만든 것", "Mine"],
NewHead: ["새 요소 — 마스터에는 안 쓰고 내 것으로만 저장", "New element — saved as mine only"],
NewName: ["이름", "Name"],
NewSpec: ["규격", "Spec"],
NewUnit: ["단위", "Unit"],
NewValue: ["값(숫자)", "Value (number)"],
Make: ["만들어 넣기", "Create and add"],
NeedName: ["이름을 적어 주세요", "Enter a name"],
NeedValue: ["값은 숫자로", "Value must be a number"],
BoxUnit: ["상자 단위", "Box unit"],
Field: ["값 칸", "Value"],
NoValue: ["—", "—"],
@@ -208,7 +182,7 @@ function renderGroup(
renderGroupRule(body, lg, box.id, group, (l, e) => addElement(l, box.id, e), rulePreview, on);
else {
const find = el("div", { className: "m02-dfip__find" });
body.replaceChildren(find, newForm(lg, box, group, on));
body.replaceChildren(find);
void drawFind(find, lg, box, group, on);
}
};
@@ -306,7 +280,7 @@ function drawChosen(
.filter((e) => e.mode !== "rule")
.map((e) => ({ id: e.ref, ...queryOf(group, e.ref) }));
if (!items.length) return;
void apiValues(items, lg.userElements).then(
void apiValues(items).then(
(answers) =>
answers.forEach((a) => {
const node = values.get(a.id);
@@ -360,26 +334,10 @@ async function drawFind(
list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Loading") }));
try {
const word = q.input.value.trim();
const [page, users] = await Promise.all([
fetchRows(file, 1, 40, word, false, sub, detail),
apiUsers().catch((): UserEl[] => []),
]);
const page = await fetchRows(file, 1, 40, word, false, sub, detail);
if (mine !== seq) return;
const known = new Map<string, UserEl>();
for (const u of [...lg.userElements, ...users])
if (u.group === group) known.set(u.id, u as UserEl);
const mineRows: M01Row[] = [...known.values()]
.filter((u) => !word || `${u.name} ${u.spec}`.includes(word))
.map((u) => ({
키: u.id,
이름: u.name,
규격: u.spec,
단위: u.unit,
값: u.value,
__mine: true,
}));
const rows = [...mineRows, ...(page.rows as M01Row[])];
count.textContent = t("Count", { n: page.total + mineRows.length, m: rows.length });
const rows = page.rows as M01Row[];
count.textContent = t("Count", { n: page.total, m: rows.length });
list.replaceChildren(
...(rows.length
? rows.map((r) => rowOf(r))
@@ -411,7 +369,6 @@ async function drawFind(
children: [
el("strong", { text: String(r["이름"] ?? "") }),
el("span", { className: "m02-dfip__muted", text: ` ${r["규격"] ?? ""}` }),
...(r["__mine"] ? [el("span", { className: "m02-dfip__tag", text: t("Mine") })] : []),
],
}),
el("span", {
@@ -448,66 +405,3 @@ async function drawFind(
);
void load();
}
/** 새 요소 — M01 표 「추가」 화면 꼴(이름 · 규격 · 단위 · 값) · 마스터에 안 쓰고 사용자 요소로 */
function newForm(
lg: CfLogic,
box: CfBox,
group: CfGroup,
on: { change(lg: CfLogic): void },
): HTMLElement {
const name = createInputField({ type: "text", label: t("NewName"), compact: true });
const spec = createInputField({ type: "text", label: t("NewSpec"), compact: true });
let unitValue = "";
const unit = el("div", { className: "m02-dfip__unitfield" });
const drawUnit = (): void =>
unit.replaceChildren(
el("span", { className: "m02-dfip__muted", text: t("NewUnit") }),
createUnitPick({
value: unitValue || NO_UNIT,
ariaLabel: t("NewUnit"),
onPick: (u) => ((unitValue = u === NO_UNIT ? "" : u), drawUnit()),
}),
);
drawUnit();
const value = createInputField({ type: "text", label: t("NewValue"), compact: true });
const go = createButton({
label: t("Make"),
onClick: () => {
const n =
value.input.value.trim() === "" ? null : Number(value.input.value.replace(/,/g, ""));
if (!name.input.value.trim()) return name.setError(t("NeedName"));
if (n !== null && !Number.isFinite(n)) return value.setError(t("NeedValue"));
name.setError(undefined);
value.setError(undefined);
apiAdd({
group,
name: name.input.value.trim(),
spec: spec.input.value.trim(),
unit: unitValue,
value: n,
}).then(
(made) => {
const el1 = elementOf(group, {
키: made.id,
이름: made.name,
규격: made.spec,
단위: made.unit,
값: made.value,
});
const withUser: CfLogic = {
...lg,
userElements: [...lg.userElements.filter((u) => u.id !== made.id), { ...made, group }],
};
on.change(addElement(withUser, box.id, el1));
},
(e) => showToast(plain(e), "error"),
);
},
});
const d = el("details", {
className: "m02-dfip__new",
children: [el("summary", { text: t("NewHead") }), name.root, spec.root, unit, value.root, go],
});
return d;
}
@@ -45,9 +45,6 @@ export const M01_OF: Record<CfGroup, { group: string; part: string }> = {
/** M01 줄 한 개 — 파일 그대로(한글 칸) */
export type M01Row = Record<string, unknown>;
/** 사용자 요소 키 머리 — 마스터가 아니라 시험 폴더 `_data` 에서 옴 */
export const USER_PREFIX = "UE-";
/** 기계 요소 한 칸의 값 이름(가격 · 손료계수 …) — ref 는 「EQ000001#가격」 */
export const MACHINE_SEP = "#";
export const machineFields = (row: M01Row): string[] =>
@@ -85,13 +82,6 @@ export function valueOf(group: CfGroup, row: M01Row, field = "가격"): number |
export function elementOf(group: CfGroup, row: M01Row, field = "가격"): CfElementRef {
const key = text(row["키"]);
const spec = text(row["규격"]);
if (key.startsWith(USER_PREFIX))
return {
ref: key,
name: [text(row["이름"]), spec].filter(Boolean).join(" "),
spec,
unit: text(row["단위"]) || "-",
};
// 환율 · 유가전력의 규격은 날짜라 알약 이름에 안 넣음(날짜가 바뀌어도 이름이 같게) — 유가전력은 상세구분(지역)으로 가름
const tail = group === "환율" ? "" : group === "유가전력" ? text(row["상세구분"]) : spec;
const named = [text(row["이름"]), tail].filter(Boolean).join(" "); // 알약 이름 = 이름 + 규격(규격만 다른 요소가 부딪히지 않게)
@@ -123,7 +113,7 @@ export function pillNames(box: CfBox): string[] {
/** 입력 값 물음 — Api `cfValues` 한 줄(id 는 부르는 쪽이 정함) */
export function queryOf(group: CfGroup, ref: string): Record<string, unknown> {
if (group === "기계" && !ref.startsWith(USER_PREFIX)) {
if (group === "기계") {
const [key, field] = ref.split(MACHINE_SEP);
return { group, ref: key, field: field || "가격" };
}
@@ -127,12 +127,9 @@ def test_값_읽기_재료는_낮은_값_요율은_비율():
assert call("valueOf", "기계", MACHINE, "가격") == 76547
def test_사용자_요소는_그_그룹의_ref_로_물음():
assert call("queryOf", "인력", "UE-0001") == {"group": "인력", "ref": "UE-0001"}
def test_값_물음은_그룹과_ref():
assert call("queryOf", "인력", "LB000033") == {"group": "인력", "ref": "LB000033"}
assert call("queryOf", "재료", "MT1") == {"group": "재료", "ref": "MT1"}
row = {"키": "UE-0001", "이름": "특수공", "규격": "", "단위": "원/일", "값": 300000}
assert call("elementOf", "인력", row)["unit"] == "원/일"
assert call("elementOf", "요율", {**row, "단위": "%"})["unit"] == "%"
def test_상자_단위는_같으면_그_단위_여럿이면_하이픈():