import type { InventoryItemDetailDto, InventoryTransactionInput, WarehouseLocationOptionDto } from "@mrp/shared/dist/inventory/types.js"; import { permissions } from "@mrp/shared"; import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { useAuth } from "../../auth/AuthProvider"; import { api, ApiError } from "../../lib/api"; import { emptyInventoryTransactionInput, inventoryTransactionOptions } from "./config"; import { InventoryAttachmentsPanel } from "./InventoryAttachmentsPanel"; import { InventoryStatusBadge } from "./InventoryStatusBadge"; import { InventoryTransactionTypeBadge } from "./InventoryTransactionTypeBadge"; import { InventoryTypeBadge } from "./InventoryTypeBadge"; export function InventoryDetailPage() { const { token, user } = useAuth(); const { itemId } = useParams(); const [item, setItem] = useState(null); const [locationOptions, setLocationOptions] = useState([]); const [transactionForm, setTransactionForm] = useState(emptyInventoryTransactionInput); const [transactionStatus, setTransactionStatus] = useState("Record receipts, issues, and adjustments against this item."); const [isSavingTransaction, setIsSavingTransaction] = useState(false); const [status, setStatus] = useState("Loading inventory item..."); const canManage = user?.permissions.includes(permissions.inventoryWrite) ?? false; useEffect(() => { if (!token || !itemId) { return; } api .getInventoryItem(token, itemId) .then((nextItem) => { setItem(nextItem); setStatus("Inventory item loaded."); }) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : "Unable to load inventory item."; setStatus(message); }); api .getWarehouseLocationOptions(token) .then((options) => { setLocationOptions(options); setTransactionForm((current) => { if (current.locationId) { return current; } const firstOption = options[0]; return firstOption ? { ...current, warehouseId: firstOption.warehouseId, locationId: firstOption.locationId, } : current; }); }) .catch(() => setLocationOptions([])); }, [itemId, token]); function updateTransactionField(key: Key, value: InventoryTransactionInput[Key]) { setTransactionForm((current) => ({ ...current, [key]: value })); } async function handleTransactionSubmit(event: React.FormEvent) { event.preventDefault(); if (!token || !itemId) { return; } setIsSavingTransaction(true); setTransactionStatus("Saving stock transaction..."); try { const nextItem = await api.createInventoryTransaction(token, itemId, transactionForm); setItem(nextItem); setTransactionStatus("Stock transaction recorded."); setTransactionForm((current) => ({ ...emptyInventoryTransactionInput, transactionType: current.transactionType, warehouseId: current.warehouseId, locationId: current.locationId, })); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : "Unable to save stock transaction."; setTransactionStatus(message); } finally { setIsSavingTransaction(false); } } if (!item) { return
{status}
; } return (

Inventory Detail

{item.sku}

{item.name}

Last updated {new Date(item.updatedAt).toLocaleString()}.

Back to items {canManage ? ( Edit item ) : null}

On Hand

{item.onHandQuantity}

Stock Locations

{item.stockBalances.length}

Transactions

{item.recentTransactions.length}

BOM Lines

{item.bomLines.length}

Item Definition

Description
{item.description || "No description provided."}
Unit of measure
{item.unitOfMeasure}
Default cost
{item.defaultCost == null ? "Not set" : `$${item.defaultCost.toFixed(2)}`}
Flags
{item.isSellable ? "Sellable" : "Not sellable"} / {item.isPurchasable ? "Purchasable" : "Not purchasable"}

Internal Notes

{item.notes || "No internal notes recorded for this item yet."}

Created {new Date(item.createdAt).toLocaleDateString()}

Current stock by location

{item.stockBalances.length === 0 ? (

No stock has been posted for this item yet.

) : (
{item.stockBalances.map((balance) => (
{balance.warehouseCode} / {balance.locationCode}
{balance.warehouseName} · {balance.locationName}
{balance.quantityOnHand}
))}
)}

Bill Of Materials

Component structure

{item.bomLines.length === 0 ? (
No BOM lines are defined for this item yet.
) : (
{item.bomLines.map((line) => ( ))}
Position Component Quantity UOM Notes
{line.position}
{line.componentSku}
{line.componentName}
{line.quantity} {line.unitOfMeasure} {line.notes || "—"}
)}
{canManage ? (

Stock Transactions

Record movement

Post receipts, issues, and adjustments to update on-hand inventory.