Files
Aislo/M02_MasterTemplete/M02_MyItems_View.ts
T
eomsangdonandClaude Opus 5.5 9b5275a78e feat(821-3): M02 내 기계 화면 · 기계 고르기 내 기계 묶음 · 복사해 내 기계로 · 새 값 · 지워짐 배지 · 내 품목
- 좌측 「내 기계」 · 「내 품목」 칸(펼치면 가운데 화면) — 목록(이름 · 규격 · 방식 · 시간당 · 쓰는 곳 · 넣은 · 고친 날 · 찾기) → 양식(821-2 부품 · 바로 넣기 기본 · 저장 때만 서버)
- 단가산출 줄 쌓기 기계 고르기에 「내 기계」 묶음 · [새 기계 만들기] · 품셈 기계 줄 「복사해 내 기계로」
- 수식 머리 배지 — 값만 바뀜 = 「새 값 있음」 · 지운 키 = 「지워짐 n」 · [단가 갱신] 확인 창에 바뀐 자원 줄
- 기계 양식 state 에 방식 · 시간당 직접 꼴 · 확인창 글 줄바꿈 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 14:21:32 +09:00

96 lines
3.9 KiB
TypeScript

/* =============================================================================
* M02_MyItems_View.ts
* M02 「내 품목」 모아 보기(PLAN 821-3 · 요구서 796 3 장 · 읽기만) — 좌측 「내 품목」 칸을 펼치면 가운데에 뜸(개인 층).
* 줄 = 그 층 단가산출 줄에 값을 바로 적은 재료(우리 자재에 안 이어진 것 · 서버 `M02_MyMachine_Look.items`)
* 칸 = 이름 · 규격 · 단위 · 값 · 쓰는 단가산출 수 · 같은 이름 수(같은 이름 묶음 = 이름 차례로 붙어 보임)
* 줄 누름 = 아래 쓰는 단가산출 목록 → 누르면 그 단가산출로(고치기는 거기서 · 여기선 저장 없음)
* ========================================================================== */
import { createDataTable } from "@ui/ui_template_data_table";
import { createInputField, el } from "@ui/ui_template_elements";
import { createFindList } from "@ui/ui_template_find_list";
import { createText } from "@ui/ui_template_text";
import { listMyItems, type MyItem, type Use } from "./M02_MyMachine_Api";
import { mt } from "./M02_MyMachine_Text";
export interface MyItemsViewOptions {
owner: string | null;
/** 그 단가산출 열기(떠남 확인은 부르는 쪽) */
openSk: (use: Use) => void;
}
export function buildMyItemsView(opt: MyItemsViewOptions): HTMLElement {
let rows: MyItem[] = [];
let shown: MyItem[] = [];
const search = createInputField({ type: "search", placeholder: mt("Find") });
const hint = createText("");
const fail = el("div");
const uses = createFindList({ placeholder: mt("Open_Sk") });
uses.root.hidden = true;
const table = createDataTable({
caption: mt("Items_Title"),
columns: [
{ key: "이름", label: mt("Name"), width: 200 },
{ key: "규격", label: mt("Spec"), width: 140 },
{ key: "단위", label: mt("Unit"), width: 60 },
{ key: "값", label: mt("Value"), kind: "number", decimals: 2, trim: true, width: 100 },
{ key: "쓰는곳", label: mt("Uses_Count"), kind: "number", decimals: 0, width: 70 },
{ key: "같은이름", label: mt("Same_Name"), kind: "number", decimals: 0, width: 70 },
],
rows: [],
empty: mt("Items_None"),
mergeSame: ["이름"],
onRowClick: (i) => pick(shown[i]),
});
const pick = (item: MyItem | undefined): void => {
if (!item) return;
uses.root.hidden = false;
uses.hint.textContent = `${item.이름} ${item.규격}`.trim();
uses.setRows(
item.쓰는곳.map((u) => ({
key: u.키,
name: u.이름 || u.키,
sub: u.키,
onPick: () => opt.openSk(u),
})),
);
};
const draw = (): void => {
const words = search.input.value.trim().toLowerCase().split(/\s+/).filter(Boolean);
const count = new Map<string, number>();
for (const r of rows) count.set(r.이름, (count.get(r.이름) ?? 0) + 1);
shown = rows.filter((r) => words.every((w) => `${r.이름} ${r.규격}`.toLowerCase().includes(w)));
hint.textContent = rows.length ? `${shown.length} / ${rows.length}` : "";
table.setRows(
shown.map((r) => ({
이름: r.이름,
규격: r.규격,
단위: r.단위,
값: typeof r.값 === "number" ? r.값 : Number(r.값) || null,
쓰는곳: r.쓰는곳.length,
같은이름: count.get(r.이름) ?? 1,
})),
);
};
search.input.addEventListener("input", draw);
void listMyItems(opt.owner)
.then((got) => (rows = got))
.catch((error) =>
fail.replaceChildren(
createText(error instanceof Error ? error.message : mt("Personal_Only"), "error"),
),
)
.finally(draw);
return el("div", {
className: "m02-mymach-view",
attrs: { "data-my-items": "" },
children: [
el("div", { className: "ui-toolbar", children: [search.root, hint] }),
createText(mt("Items_Note"), "muted"),
fail,
table.root,
uses.root,
],
});
}