feat(m02-cf): InputPanel 완성 — 서버 Api 최종 꼴(ref · Model 사용자 요소)에 맞춤 · 환율 유가전력 알약 이름
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
This commit is contained in:
@@ -20,11 +20,12 @@ import {
|
||||
fetchSubs,
|
||||
type SubInfo,
|
||||
} from "../../M01_MasterData/M01_MasterData_Api_Fetch";
|
||||
import { cfPlain, cfUserElementAdd, cfUserElements, cfValues, type CfQuery } from "./M02_CF_Api";
|
||||
import type { CfBox, CfGroup, CfLogic } from "./M02_CF_Model";
|
||||
import { cfPlain, cfUserElementAdd, cfUserElements, cfValues } from "./M02_CF_Api";
|
||||
import type { CfBox, CfElementRef, CfGroup, CfLogic } from "./M02_CF_Model";
|
||||
import {
|
||||
GROUPS,
|
||||
M01_OF,
|
||||
MACHINE_SEP,
|
||||
USER_PREFIX,
|
||||
addElement,
|
||||
elementOf,
|
||||
@@ -39,6 +40,24 @@ import { renderTablePick } from "./M02_CF_InputPanel_Table";
|
||||
import { renderVariables } from "./M02_CF_InputPanel_Vars";
|
||||
import "./M02_CF_InputPanel.css";
|
||||
|
||||
/** Api 모양 — 서버 Api 가 채워지면(사용자 요소 = Model 모양 · 값 물음 = ref) 그 모양을 그대로 씀 */
|
||||
interface Answer {
|
||||
id: string;
|
||||
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?"],
|
||||
Variable: ["변수", "Variable"],
|
||||
@@ -202,18 +221,47 @@ function drawChosen(
|
||||
: [el("p", { className: "m02-cfip__muted", text: t("NoneChosen") })]),
|
||||
);
|
||||
if (!els.length) return;
|
||||
const items = els.map((e) => ({ id: e.ref, ...queryOf(group, e.ref) }) as unknown as CfQuery);
|
||||
void cfValues(items).then(
|
||||
const items = els.map((e) => ({ id: e.ref, ...queryOf(group, e.ref) }));
|
||||
void apiValues(items, lg.userElements).then(
|
||||
(answers) =>
|
||||
answers.forEach((a) => {
|
||||
const node = values.get(a.id);
|
||||
if (node && a.ok && a.value !== null && a.value !== undefined)
|
||||
node.textContent = typeof a.value === "number" ? fmt(a.value) : String(a.value);
|
||||
}),
|
||||
() => undefined, // 값을 못 받아도 요소는 넣을 수 있음 — 값 칸만 「—」
|
||||
// Api 가 아직 값을 못 주면 M01 줄에서 바로 읽어 보임(값 칸만 — 요소는 그대로)
|
||||
() =>
|
||||
els.forEach(
|
||||
(e) =>
|
||||
void m01Value(group, e).then((v) => {
|
||||
const node = values.get(e.ref);
|
||||
if (node && v !== null) node.textContent = fmt(v);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/** 요소 한 개의 값을 M01 줄에서 읽음 — 이름으로 찾아 키가 같은 줄 · 못 찾으면 null */
|
||||
async function m01Value(group: CfGroup, e: CfElementRef): Promise<number | null> {
|
||||
if (e.ref.startsWith(USER_PREFIX)) return null;
|
||||
try {
|
||||
const [key, field] = e.ref.split(MACHINE_SEP);
|
||||
const page = await fetchRows(
|
||||
await fileOf(group),
|
||||
1,
|
||||
30,
|
||||
e.name.split(" · ")[0].split(" ")[0],
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
);
|
||||
const row = (page.rows as M01Row[]).find((r) => r["키"] === key);
|
||||
return row ? valueOf(group, row, field || "가격") : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** M01 줄 찾기 — 구분 · 상세구분 · 이름 · 내가 만든 것도 함께 */
|
||||
async function drawFind(
|
||||
host: HTMLElement,
|
||||
@@ -259,21 +307,20 @@ async function drawFind(
|
||||
const word = q.input.value.trim();
|
||||
const [page, users] = await Promise.all([
|
||||
fetchRows(file, 1, 40, word, false, sub, detail),
|
||||
cfUserElements()
|
||||
.then((u) =>
|
||||
u.filter((x) => (x.group as string) === group || (x.group as string) === "사용자"),
|
||||
)
|
||||
.catch(() => []),
|
||||
apiUsers().catch((): UserEl[] => []),
|
||||
]);
|
||||
if (mine !== seq) return;
|
||||
const mineRows: M01Row[] = users
|
||||
.filter((u) => !word || `${u.이름} ${u.규격}`.includes(word))
|
||||
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.key,
|
||||
이름: u.이름,
|
||||
규격: u.규격,
|
||||
단위: u.단위,
|
||||
값: u.값,
|
||||
키: u.id,
|
||||
이름: u.name,
|
||||
규격: u.spec,
|
||||
단위: u.unit,
|
||||
값: u.value,
|
||||
__mine: true,
|
||||
}));
|
||||
const rows = [...mineRows, ...(page.rows as M01Row[])];
|
||||
@@ -368,34 +415,24 @@ function newForm(
|
||||
if (n !== null && !Number.isFinite(n)) return value.setError(t("NeedValue"));
|
||||
name.setError(undefined);
|
||||
value.setError(undefined);
|
||||
cfUserElementAdd({
|
||||
apiAdd({
|
||||
group,
|
||||
이름: name.input.value.trim(),
|
||||
규격: spec.input.value.trim(),
|
||||
단위: unit.input.value.trim(),
|
||||
값: n,
|
||||
name: name.input.value.trim(),
|
||||
spec: spec.input.value.trim(),
|
||||
unit: unit.input.value.trim(),
|
||||
value: n,
|
||||
}).then(
|
||||
(made) => {
|
||||
const el1 = elementOf(group, {
|
||||
키: made.key,
|
||||
이름: made.이름,
|
||||
규격: made.규격,
|
||||
단위: made.단위,
|
||||
값: made.값,
|
||||
키: made.id,
|
||||
이름: made.name,
|
||||
규격: made.spec,
|
||||
단위: made.unit,
|
||||
값: made.value,
|
||||
});
|
||||
const withUser: CfLogic = {
|
||||
...lg,
|
||||
userElements: [
|
||||
...lg.userElements,
|
||||
{
|
||||
id: made.key,
|
||||
group,
|
||||
name: made.이름,
|
||||
spec: made.규격,
|
||||
unit: made.단위,
|
||||
value: made.값,
|
||||
},
|
||||
],
|
||||
userElements: [...lg.userElements.filter((u) => u.id !== made.id), { ...made, group }],
|
||||
};
|
||||
on.change(addElement(withUser, box.id, el1));
|
||||
},
|
||||
|
||||
@@ -80,7 +80,9 @@ export function elementOf(group: CfGroup, row: M01Row, field = "가격"): CfElem
|
||||
spec,
|
||||
unit: text(row["단위"]) || "-",
|
||||
};
|
||||
const named = [text(row["이름"]), spec].filter(Boolean).join(" "); // 알약 이름 = 이름 + 규격(규격만 다른 요소가 부딪히지 않게)
|
||||
// 환율 · 유가전력의 규격은 날짜라 알약 이름에 안 넣음(날짜가 바뀌어도 이름이 같게) — 유가전력은 상세구분(지역)으로 가름
|
||||
const tail = group === "환율" ? "" : group === "유가전력" ? text(row["상세구분"]) : spec;
|
||||
const named = [text(row["이름"]), tail].filter(Boolean).join(" "); // 알약 이름 = 이름 + 규격(규격만 다른 요소가 부딪히지 않게)
|
||||
if (group === "기계")
|
||||
return {
|
||||
ref: `${key}${MACHINE_SEP}${field}`,
|
||||
@@ -108,12 +110,11 @@ export function pillNames(box: CfBox): string[] {
|
||||
|
||||
/** 입력 값 물음 — Api `cfValues` 한 줄(id 는 부르는 쪽이 정함) */
|
||||
export function queryOf(group: CfGroup, ref: string): Record<string, unknown> {
|
||||
if (ref.startsWith(USER_PREFIX)) return { group: "사용자", key: ref };
|
||||
if (group === "기계") {
|
||||
if (group === "기계" && !ref.startsWith(USER_PREFIX)) {
|
||||
const [key, field] = ref.split(MACHINE_SEP);
|
||||
return { group, key, field: field || "가격" };
|
||||
return { group, ref: key, field: field || "가격" };
|
||||
}
|
||||
return { group, key: ref };
|
||||
return { group, ref };
|
||||
}
|
||||
|
||||
/** 상자 단위 — 모두 같으면 그 단위 · 여럿이거나 없으면 「-」 */
|
||||
|
||||
Reference in New Issue
Block a user