- 좌측 「내 기계」 · 「내 품목」 칸(펼치면 가운데 화면) — 목록(이름 · 규격 · 방식 · 시간당 · 쓰는 곳 · 넣은 · 고친 날 · 찾기) → 양식(821-2 부품 · 바로 넣기 기본 · 저장 때만 서버) - 단가산출 줄 쌓기 기계 고르기에 「내 기계」 묶음 · [새 기계 만들기] · 품셈 기계 줄 「복사해 내 기계로」 - 수식 머리 배지 — 값만 바뀜 = 「새 값 있음」 · 지운 키 = 「지워짐 n」 · [단가 갱신] 확인 창에 바뀐 자원 줄 - 기계 양식 state 에 방식 · 시간당 직접 꼴 · 확인창 글 줄바꿈 그대로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.9 KiB
TypeScript
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,
|
|
],
|
|
});
|
|
}
|