sku auto collapse
This commit is contained in:
@@ -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,39 +72,66 @@ 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) => (
|
||||
<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="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>
|
||||
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 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={() =>
|
||||
setNodeForm((current) => ({
|
||||
...current,
|
||||
familyId: selectedFamilyId,
|
||||
parentNodeId: node.id,
|
||||
}))
|
||||
}
|
||||
className="rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text"
|
||||
>
|
||||
Add child
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setNodeForm((current) => ({
|
||||
...current,
|
||||
familyId: selectedFamilyId,
|
||||
parentNodeId: node.id,
|
||||
}))
|
||||
}
|
||||
className="rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text"
|
||||
>
|
||||
Add child
|
||||
</button>
|
||||
{node.description ? <div className="mt-2 text-xs text-muted">{node.description}</div> : null}
|
||||
</div>
|
||||
{node.description ? <div className="mt-2 text-xs text-muted">{node.description}</div> : null}
|
||||
{isExpanded ? renderNodes(node.id, depth + 1) : null}
|
||||
</div>
|
||||
{renderNodes(node.id, depth + 1)}
|
||||
</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 ${
|
||||
|
||||
Reference in New Issue
Block a user