import type { InventoryBomLineInput, InventoryItemInput, InventoryItemOptionDto } from "@mrp/shared/dist/inventory/types.js"; import { useEffect, useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { useAuth } from "../../auth/AuthProvider"; import { api, ApiError } from "../../lib/api"; import { emptyInventoryBomLineInput, emptyInventoryItemInput, inventoryStatusOptions, inventoryTypeOptions, inventoryUnitOptions } from "./config"; interface InventoryFormPageProps { mode: "create" | "edit"; } export function InventoryFormPage({ mode }: InventoryFormPageProps) { const navigate = useNavigate(); const { token } = useAuth(); const { itemId } = useParams(); const [form, setForm] = useState(emptyInventoryItemInput); const [componentOptions, setComponentOptions] = useState([]); const [componentSearchTerms, setComponentSearchTerms] = useState([]); const [activeComponentPicker, setActiveComponentPicker] = useState(null); const [status, setStatus] = useState(mode === "create" ? "Create a new inventory item." : "Loading inventory item..."); const [isSaving, setIsSaving] = useState(false); function getComponentOption(componentItemId: string) { return componentOptions.find((option) => option.id === componentItemId) ?? null; } function getComponentSku(componentItemId: string) { return getComponentOption(componentItemId)?.sku ?? ""; } useEffect(() => { if (!token) { return; } api .getInventoryItemOptions(token) .then((options) => { const nextOptions = options.filter((option) => option.id !== itemId); setComponentOptions(nextOptions); setComponentSearchTerms((current) => form.bomLines.map((line, index) => { if (current[index]?.trim()) { return current[index]; } const match = nextOptions.find((option) => option.id === line.componentItemId); return match ? match.sku : ""; }) ); }) .catch(() => setComponentOptions([])); }, [form.bomLines, itemId, token]); useEffect(() => { if (mode !== "edit" || !token || !itemId) { return; } api .getInventoryItem(token, itemId) .then((item) => { setForm({ sku: item.sku, name: item.name, description: item.description, type: item.type, status: item.status, unitOfMeasure: item.unitOfMeasure, isSellable: item.isSellable, isPurchasable: item.isPurchasable, defaultCost: item.defaultCost, notes: item.notes, bomLines: item.bomLines.map((line) => ({ componentItemId: line.componentItemId, quantity: line.quantity, unitOfMeasure: line.unitOfMeasure, notes: line.notes, position: line.position, })), }); setComponentSearchTerms(item.bomLines.map((line) => line.componentSku)); setStatus("Inventory item loaded."); }) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : "Unable to load inventory item."; setStatus(message); }); }, [itemId, mode, token]); function updateField(key: Key, value: InventoryItemInput[Key]) { setForm((current) => ({ ...current, [key]: value })); } function updateBomLine(index: number, nextLine: InventoryBomLineInput) { setForm((current) => ({ ...current, bomLines: current.bomLines.map((line, lineIndex) => (lineIndex === index ? nextLine : line)), })); } function updateComponentSearchTerm(index: number, value: string) { setComponentSearchTerms((current) => { const nextTerms = [...current]; nextTerms[index] = value; return nextTerms; }); } function addBomLine() { setForm((current) => ({ ...current, bomLines: [ ...current.bomLines, { ...emptyInventoryBomLineInput, position: current.bomLines.length === 0 ? 10 : Math.max(...current.bomLines.map((line) => line.position)) + 10, }, ], })); setComponentSearchTerms((current) => [...current, ""]); } function removeBomLine(index: number) { setForm((current) => ({ ...current, bomLines: current.bomLines.filter((_line, lineIndex) => lineIndex !== index), })); setComponentSearchTerms((current) => current.filter((_term, termIndex) => termIndex !== index)); setActiveComponentPicker((current) => (current === index ? null : current != null && current > index ? current - 1 : current)); } async function handleSubmit(event: React.FormEvent) { event.preventDefault(); if (!token) { return; } setIsSaving(true); setStatus("Saving inventory item..."); try { const saved = mode === "create" ? await api.createInventoryItem(token, form) : await api.updateInventoryItem(token, itemId ?? "", form); navigate(`/inventory/items/${saved.id}`); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : "Unable to save inventory item."; setStatus(message); setIsSaving(false); } } return (

Inventory Editor

{mode === "create" ? "New Item" : "Edit Item"}

Define item master data and the first revision of the bill of materials for assemblies and manufactured items.

Cancel