diff --git a/frontend/src/components/inventory/NodeContextPanel.tsx b/frontend/src/components/inventory/NodeContextPanel.tsx index 182b7a6..cf69090 100644 --- a/frontend/src/components/inventory/NodeContextPanel.tsx +++ b/frontend/src/components/inventory/NodeContextPanel.tsx @@ -1,31 +1,14 @@ import type { NodeContext } from '../../types/inventory'; +import { RelationNavigator } from './RelationNavigator'; import './inventory.css'; type Props = { context: NodeContext | null; loading?: boolean; + onNavigateNode: (id: string) => void; }; -function RelationList({ title, items }: { title: string; items: NodeContext['relations']['incoming'] }) { - return ( -
-

{title}

- {items.length === 0 ? ( -

Nessuna relazione

- ) : ( - - )} -
- ); -} - -export function NodeContextPanel({ context, loading }: Props) { +export function NodeContextPanel({ context, loading, onNavigateNode }: Props) { if (loading) { return (
@@ -56,12 +39,14 @@ export function NodeContextPanel({ context, loading }: Props) {

Physical

-

Parent: {parent ? parent.name : 'Nessuno'}

+

Parent: {parent ? : 'Nessuno'}

Children: {children.length}

{children.length > 0 && (
    {children.map((child) => ( -
  • {child.name} · {child.type}
  • +
  • + · {child.type} +
  • ))}
)} @@ -76,8 +61,15 @@ export function NodeContextPanel({ context, loading }: Props) {
- - +
+

Outgoing relations

+ +
+ +
+

Incoming relations

+ +
); } diff --git a/frontend/src/components/inventory/RelationNavigator.tsx b/frontend/src/components/inventory/RelationNavigator.tsx new file mode 100644 index 0000000..aef8b89 --- /dev/null +++ b/frontend/src/components/inventory/RelationNavigator.tsx @@ -0,0 +1,30 @@ +import type { Relation } from '../../types/inventory'; +import './inventory.css'; + +type Props = { + relations: Relation[]; + onNavigate: (id: string) => void; +}; + +export function RelationNavigator({ relations, onNavigate }: Props) { + if (relations.length === 0) { + return

Nessuna relazione navigabile.

; + } + + return ( + + ); +} diff --git a/frontend/src/components/inventory/inventory.css b/frontend/src/components/inventory/inventory.css index 5879e03..756ebc6 100644 --- a/frontend/src/components/inventory/inventory.css +++ b/frontend/src/components/inventory/inventory.css @@ -46,7 +46,8 @@ } .inventory-tree-toggle, -.inventory-tree-label { +.inventory-tree-label, +.inventory-link-button { background: transparent; border: 0; color: inherit; @@ -96,6 +97,7 @@ background: rgba(255,255,255,0.03); border-radius: 10px; padding: 12px; + margin-bottom: 12px; } .inventory-list { @@ -113,6 +115,32 @@ color: #cbd5e1; } +.inventory-link-button.inline { + padding: 0; + color: #93c5fd; + text-decoration: underline; + text-underline-offset: 2px; +} + +.inventory-link-button.inline:hover, +.inventory-link-button:hover { + color: #bfdbfe; +} + +.inventory-relation-list { + padding-left: 0; + list-style: none; +} + +.inventory-relation-item { + margin-top: 8px; +} + +.inventory-relation-arrow { + margin: 0 6px; + color: #94a3b8; +} + @media (max-width: 960px) { .inventory-layout { grid-template-columns: 1fr; diff --git a/frontend/src/lib/inventoryApi.ts b/frontend/src/lib/inventoryApi.ts index b1033fa..555203f 100644 --- a/frontend/src/lib/inventoryApi.ts +++ b/frontend/src/lib/inventoryApi.ts @@ -1,4 +1,4 @@ -import type { EntityResponse, InventoryTreeNode, ListResponse, NodeContext } from '../types/inventory'; +import type { EntityResponse, InventoryTreeNode, ListResponse, NodeContext, Relation } from '../types/inventory'; async function handleResponse(response: Response): Promise { if (!response.ok) { @@ -21,3 +21,8 @@ export async function fetchNodeContext(id: string): Promise { const data = await fetchJson>(`/inventory/nodes/${id}/context`); return data.item; } + +export async function fetchNodeRelations(id: string): Promise { + const data = await fetchJson>(`/inventory/nodes/${id}/relations`); + return data.items; +} diff --git a/frontend/src/pages/InventoryPage.tsx b/frontend/src/pages/InventoryPage.tsx index b532eef..9df8c6d 100644 --- a/frontend/src/pages/InventoryPage.tsx +++ b/frontend/src/pages/InventoryPage.tsx @@ -20,6 +20,19 @@ export function InventoryPage() { const [loadingContext, setLoadingContext] = useState(false); const [error, setError] = useState(null); + const loadContext = async (id: string) => { + setSelectedId(id); + setLoadingContext(true); + try { + const item = await fetchNodeContext(id); + setContext(item); + } catch (err) { + setError(err instanceof Error ? err.message : 'Errore sconosciuto'); + } finally { + setLoadingContext(false); + } + }; + useEffect(() => { let active = true; setLoadingTree(true); @@ -28,7 +41,7 @@ export function InventoryPage() { if (!active) return; setNodes(items); const first = findFirstNode(items); - setSelectedId(first); + if (first) loadContext(first); }) .catch((err: Error) => { if (!active) return; @@ -42,25 +55,6 @@ export function InventoryPage() { }; }, []); - useEffect(() => { - if (!selectedId) return; - let active = true; - setLoadingContext(true); - fetchNodeContext(selectedId) - .then((item) => { - if (active) setContext(item); - }) - .catch((err: Error) => { - if (active) setError(err.message); - }) - .finally(() => { - if (active) setLoadingContext(false); - }); - return () => { - active = false; - }; - }, [selectedId]); - if (loadingTree) { return
Caricamento inventory...
; } @@ -71,8 +65,8 @@ export function InventoryPage() { return (
- - + +
); }