sku auto collapse

This commit is contained in:
2026-03-16 23:34:24 -05:00
parent a1b5d7aa84
commit c6931d5c5d

View File

@@ -1,5 +1,5 @@
import { permissions } from "@mrp/shared";
import type { InventorySkuCatalogTreeDto, InventorySkuFamilyInput, InventorySkuNodeDto, InventorySkuNodeInput } from "@mrp/shared/dist/inventory/types.js";
import type { InventorySkuCatalogTreeDto, InventorySkuFamilyInput, InventorySkuNodeInput } from "@mrp/shared/dist/inventory/types.js";
import type { ReactNode } from "react";
import { useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom";
@@ -31,6 +31,7 @@ export function InventorySkuMasterPage() {
const [familyForm, setFamilyForm] = useState<InventorySkuFamilyInput>(emptyFamilyForm);
const [nodeForm, setNodeForm] = useState<InventorySkuNodeInput>(emptyNodeForm);
const [selectedFamilyId, setSelectedFamilyId] = useState("");
const [expandedNodeIds, setExpandedNodeIds] = useState<string[]>([]);
const [status, setStatus] = useState("Loading SKU master...");
const canManage = user?.permissions.includes(permissions.inventoryWrite) ?? false;
@@ -46,6 +47,7 @@ export function InventorySkuMasterPage() {
setCatalog(nextCatalog);
const firstFamilyId = nextCatalog.families[0]?.id ?? "";
setSelectedFamilyId((current) => current || firstFamilyId);
setExpandedNodeIds([]);
setNodeForm((current) => ({
...current,
familyId: current.familyId || firstFamilyId,
@@ -70,20 +72,46 @@ export function InventorySkuMasterPage() {
[familyNodes]
);
function toggleNode(nodeId: string) {
setExpandedNodeIds((current) => (current.includes(nodeId) ? current.filter((id) => id !== nodeId) : [...current, nodeId]));
}
function renderNodes(parentNodeId: string | null, depth = 0): ReactNode {
const branchNodes = familyNodes.filter((node) => node.parentNodeId === parentNodeId);
if (!branchNodes.length) {
return null;
}
return branchNodes.map((node) => (
return branchNodes.map((node) => {
const isExpanded = expandedNodeIds.includes(node.id);
return (
<div key={node.id} className="space-y-2">
<div className="rounded-[18px] border border-line/70 bg-page/60 px-3 py-3" style={{ marginLeft: `${depth * 16}px` }}>
<div className="flex flex-wrap items-center justify-between gap-2">
<div>
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
{node.childCount > 0 ? (
<button
type="button"
onClick={() => toggleNode(node.id)}
className="inline-flex h-7 w-7 items-center justify-center rounded-xl border border-line/70 text-sm font-semibold text-text transition hover:bg-page/80"
aria-expanded={isExpanded}
aria-label={`${isExpanded ? "Collapse" : "Expand"} ${node.code}`}
>
{isExpanded ? "-" : "+"}
</button>
) : (
<span className="inline-flex h-7 w-7 items-center justify-center rounded-xl border border-dashed border-line/50 text-xs text-muted">
o
</span>
)}
<div className="min-w-0">
<div className="text-sm font-semibold text-text">{node.code} <span className="text-muted">- {node.label}</span></div>
<div className="mt-1 text-xs text-muted">Level {node.level} {node.childCount} child branch(es)</div>
</div>
</div>
</div>
<button
type="button"
onClick={() =>
@@ -100,9 +128,10 @@ export function InventorySkuMasterPage() {
</div>
{node.description ? <div className="mt-2 text-xs text-muted">{node.description}</div> : null}
</div>
{renderNodes(node.id, depth + 1)}
{isExpanded ? renderNodes(node.id, depth + 1) : null}
</div>
));
);
});
}
async function reloadCatalog() {
@@ -127,6 +156,7 @@ export function InventorySkuMasterPage() {
const created = await api.createInventorySkuFamily(token, familyForm);
setFamilyForm(emptyFamilyForm);
setSelectedFamilyId(created.id);
setExpandedNodeIds([]);
setNodeForm((current) => ({ ...current, familyId: created.id, parentNodeId: null }));
await reloadCatalog();
setStatus(`Created SKU family ${created.code}.`);
@@ -143,6 +173,13 @@ export function InventorySkuMasterPage() {
try {
const created = await api.createInventorySkuNode(token, nodeForm);
setExpandedNodeIds((current) => {
if (!created.parentNodeId || current.includes(created.parentNodeId)) {
return current;
}
return [...current, created.parentNodeId];
});
setNodeForm((current) => ({
...emptyNodeForm,
familyId: current.familyId,
@@ -184,6 +221,7 @@ export function InventorySkuMasterPage() {
type="button"
onClick={() => {
setSelectedFamilyId(family.id);
setExpandedNodeIds([]);
setNodeForm((current) => ({ ...current, familyId: family.id, parentNodeId: null }));
}}
className={`block w-full rounded-[18px] border px-3 py-3 text-left transition ${