Business Logic Components
Domain-specific components, data-driven UI, and business logic integration for Smart Shelf features.
Business Logic Components
Specialized components that encapsulate Smart Shelf's business logic, domain-specific functionality, and data-driven user interfaces.
Product Management Components
ProductForm Component
Comprehensive product creation and editing form with validation:
interface ProductFormProps {
product?: Product;
onSubmit: (data: ProductFormData) => Promise<void>;
onCancel: () => void;
}
export function ProductForm({ product, onSubmit, onCancel }: ProductFormProps) {
const form = useForm<ProductFormData>({
resolver: zodResolver(productSchema),
defaultValues: product || defaultProductValues,
});
const handleSubmit = async (data: ProductFormData) => {
try {
await onSubmit(data);
toast.success(product ? 'Product updated successfully' : 'Product created successfully');
} catch (error) {
form.setError('root', { message: 'Failed to save product' });
toast.error('Failed to save product');
}
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
<FormSection title="Basic Information">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormFieldControlled
name="name"
label="Product Name"
required
>
{(field) => <Input {...field} placeholder="Enter product name" />}
</FormFieldControlled>
<FormFieldControlled
name="sku"
label="SKU"
required
description="Stock Keeping Unit - must be unique"
>
{(field) => <Input {...field} placeholder="Enter SKU" />}
</FormFieldControlled>
</div>
<FormFieldControlled
name="description"
label="Description"
>
{(field) => (
<Textarea
{...field}
placeholder="Enter product description"
rows={3}
/>
)}
</FormFieldControlled>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormFieldControlled
name="categoryId"
label="Category"
required
>
{(field) => (
<CategorySelect
value={field.value}
onValueChange={field.onChange}
/>
)}
</FormFieldControlled>
<FormFieldControlled
name="brand"
label="Brand"
>
{(field) => <Input {...field} placeholder="Enter brand" />}
</FormFieldControlled>
</div>
</FormSection>
<FormSection title="Pricing & Inventory">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<FormFieldControlled
name="costPrice"
label="Cost Price"
required
>
{(field) => (
<Input
{...field}
type="number"
step="0.01"
placeholder="0.00"
onChange={(e) => field.onChange(parseFloat(e.target.value))}
/>
)}
</FormFieldControlled>
<FormFieldControlled
name="sellingPrice"
label="Selling Price"
required
>
{(field) => (
<Input
{...field}
type="number"
step="0.01"
placeholder="0.00"
onChange={(e) => field.onChange(parseFloat(e.target.value))}
/>
)}
</FormFieldControlled>
<FormFieldControlled
name="unitOfMeasure"
label="Unit of Measure"
required
>
{(field) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder="Select unit" />
</SelectTrigger>
<SelectContent>
<SelectItem value="piece">Piece</SelectItem>
<SelectItem value="kg">Kilogram</SelectItem>
<SelectItem value="liter">Liter</SelectItem>
<SelectItem value="box">Box</SelectItem>
</SelectContent>
</Select>
)}
</FormFieldControlled>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormFieldControlled
name="minStockLevel"
label="Minimum Stock Level"
description="Alert when stock falls below this level"
>
{(field) => (
<Input
{...field}
type="number"
min="0"
onChange={(e) => field.onChange(parseInt(e.target.value))}
/>
)}
</FormFieldControlled>
<FormFieldControlled
name="maxStockLevel"
label="Maximum Stock Level"
description="Maximum recommended stock level"
>
{(field) => (
<Input
{...field}
type="number"
min="0"
onChange={(e) => field.onChange(parseInt(e.target.value))}
/>
)}
</FormFieldControlled>
</div>
</FormSection>
<FormSection title="Barcodes" collapsible>
<BarcodeFieldArray />
</FormSection>
<div className="flex justify-end space-x-2">
<Button type="button" variant="outline" onClick={onCancel}>
Cancel
</Button>
<LoadingButton
type="submit"
loading={form.formState.isSubmitting}
>
{product ? 'Update Product' : 'Create Product'}
</LoadingButton>
</div>
</form>
</Form>
);
}
ProductCard Component
Product display card with actions:
interface ProductCardProps {
product: Product;
onEdit?: () => void;
onDelete?: () => void;
onViewDetails?: () => void;
showActions?: boolean;
}
export function ProductCard({
product,
onEdit,
onDelete,
onViewDetails,
showActions = true
}: ProductCardProps) {
const [isDeleting, setIsDeleting] = useState(false);
const handleDelete = async () => {
if (!onDelete) return;
const confirmed = await confirm({
title: 'Delete Product',
description: `Are you sure you want to delete "${product.name}"? This action cannot be undone.`,
confirmText: 'Delete',
variant: 'destructive',
});
if (confirmed) {
setIsDeleting(true);
try {
await onDelete();
toast.success('Product deleted successfully');
} catch (error) {
toast.error('Failed to delete product');
} finally {
setIsDeleting(false);
}
}
};
return (
<Card className="group hover:shadow-md transition-shadow">
<CardContent className="p-4">
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<h3 className="font-semibold text-lg truncate">{product.name}</h3>
<ProductStatusBadge status={product.status} />
</div>
<p className="text-sm text-muted-foreground mb-2">
SKU: {product.sku}
</p>
{product.description && (
<p className="text-sm text-muted-foreground line-clamp-2 mb-3">
{product.description}
</p>
)}
<div className="flex items-center justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">
${product.sellingPrice.toFixed(2)}
</span>
<span className="text-xs text-muted-foreground">
Cost: ${product.costPrice.toFixed(2)}
</span>
</div>
<div className="flex items-center gap-1">
<Package className="h-3 w-3 text-muted-foreground" />
<span className="text-xs text-muted-foreground">
{product.currentStock} {product.unitOfMeasure}
</span>
{product.currentStock <= product.minStockLevel && (
<Badge variant="destructive" className="text-xs">
Low Stock
</Badge>
)}
</div>
</div>
{showActions && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{onViewDetails && (
<DropdownMenuItem onClick={onViewDetails}>
<Eye className="h-4 w-4 mr-2" />
View Details
</DropdownMenuItem>
)}
{onEdit && (
<DropdownMenuItem onClick={onEdit}>
<Edit className="h-4 w-4 mr-2" />
Edit
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
{onDelete && (
<DropdownMenuItem
onClick={handleDelete}
disabled={isDeleting}
className="text-red-600"
>
<Trash2 className="h-4 w-4 mr-2" />
Delete
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</div>
</div>
</CardContent>
</Card>
);
}
Inventory Management Components
InventoryTable Component
Specialized inventory data table with stock management:
interface InventoryTableProps {
warehouseId?: string;
filters?: InventoryFilters;
onStockAdjustment: (productId: string, adjustment: StockAdjustment) => void;
}
export function InventoryTable({ warehouseId, filters, onStockAdjustment }: InventoryTableProps) {
const { data: inventory, loading, error, refetch } = useInventory({
warehouseId,
filters,
});
const columns: ColumnDef<InventoryItem>[] = [
{
accessorKey: 'product.name',
header: 'Product',
cell: ({ row }) => (
<div className="flex items-center space-x-3">
<ProductImage
src={row.original.product.image}
alt={row.original.product.name}
size="sm"
/>
<div>
<p className="font-medium">{row.original.product.name}</p>
<p className="text-sm text-muted-foreground">
{row.original.product.sku}
</p>
</div>
</div>
),
},
{
accessorKey: 'quantityOnHand',
header: 'On Hand',
cell: ({ row }) => (
<StockLevel
quantity={row.original.quantityOnHand}
minLevel={row.original.product.minStockLevel}
unit={row.original.product.unitOfMeasure}
/>
),
},
{
accessorKey: 'quantityAllocated',
header: 'Allocated',
cell: ({ row }) => (
<span className="text-sm">
{row.original.quantityAllocated} {row.original.product.unitOfMeasure}
</span>
),
},
{
accessorKey: 'quantityAvailable',
header: 'Available',
cell: ({ row }) => (
<span className={cn(
"text-sm font-medium",
row.original.quantityAvailable <= 0 ? "text-red-600" : "text-green-600"
)}>
{row.original.quantityAvailable} {row.original.product.unitOfMeasure}
</span>
),
},
{
accessorKey: 'lastUpdated',
header: 'Last Updated',
cell: ({ row }) => (
<span className="text-sm text-muted-foreground">
{formatDistanceToNow(new Date(row.original.lastUpdated), { addSuffix: true })}
</span>
),
},
{
id: 'actions',
header: 'Actions',
cell: ({ row }) => (
<InventoryActions
item={row.original}
onStockAdjustment={onStockAdjustment}
/>
),
},
];
if (loading) return <DataTableSkeleton columns={6} rows={10} />;
if (error) return <ErrorMessage error={error} onRetry={refetch} />;
return (
<div className="space-y-4">
<DataTable
data={inventory}
columns={columns}
searchable
filterable
exportable
pagination={{
pageSize: 50,
showSizeSelector: true,
}}
/>
</div>
);
}
StockAdjustmentForm Component
Form for adjusting inventory levels:
interface StockAdjustmentFormProps {
product: Product;
currentStock: number;
onSubmit: (adjustment: StockAdjustment) => Promise<void>;
onCancel: () => void;
}
export function StockAdjustmentForm({
product,
currentStock,
onSubmit,
onCancel
}: StockAdjustmentFormProps) {
const form = useForm<StockAdjustmentData>({
resolver: zodResolver(stockAdjustmentSchema),
defaultValues: {
adjustmentType: 'set',
quantity: currentStock,
reason: '',
notes: '',
},
});
const adjustmentType = form.watch('adjustmentType');
const quantity = form.watch('quantity');
const calculateNewStock = () => {
switch (adjustmentType) {
case 'add':
return currentStock + quantity;
case 'subtract':
return currentStock - quantity;
case 'set':
return quantity;
default:
return currentStock;
}
};
const newStock = calculateNewStock();
const difference = newStock - currentStock;
const handleSubmit = async (data: StockAdjustmentData) => {
try {
await onSubmit({
...data,
currentStock,
newStock,
difference,
});
toast.success('Stock adjustment completed successfully');
} catch (error) {
toast.error('Failed to adjust stock');
}
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
<div className="space-y-4">
<div className="flex items-center gap-4 p-4 bg-muted rounded-lg">
<div>
<h4 className="font-medium">{product.name}</h4>
<p className="text-sm text-muted-foreground">SKU: {product.sku}</p>
</div>
<div className="ml-auto text-right">
<p className="text-sm text-muted-foreground">Current Stock</p>
<p className="text-lg font-semibold">
{currentStock} {product.unitOfMeasure}
</p>
</div>
</div>
<FormFieldControlled
name="adjustmentType"
label="Adjustment Type"
required
>
{(field) => (
<RadioGroup value={field.value} onValueChange={field.onChange}>
<div className="space-y-2">
<RadioGroupItem value="add" label="Add to current stock" />
<RadioGroupItem value="subtract" label="Subtract from current stock" />
<RadioGroupItem value="set" label="Set exact stock level" />
</div>
</RadioGroup>
)}
</FormFieldControlled>
<FormFieldControlled
name="quantity"
label={
adjustmentType === 'set'
? 'New Stock Level'
: `Quantity to ${adjustmentType}`
}
required
>
{(field) => (
<div className="space-y-2">
<Input
{...field}
type="number"
min="0"
onChange={(e) => field.onChange(parseInt(e.target.value) || 0)}
/>
<div className="text-sm text-muted-foreground">
New stock will be: <strong>{newStock} {product.unitOfMeasure}</strong>
{difference !== 0 && (
<span className={difference > 0 ? 'text-green-600' : 'text-red-600'}>
{' '}({difference > 0 ? '+' : ''}{difference})
</span>
)}
</div>
</div>
)}
</FormFieldControlled>
<FormFieldControlled
name="reason"
label="Reason"
required
>
{(field) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder="Select reason" />
</SelectTrigger>
<SelectContent>
<SelectItem value="physical_count">Physical Count</SelectItem>
<SelectItem value="damage">Damaged Items</SelectItem>
<SelectItem value="expired">Expired Items</SelectItem>
<SelectItem value="theft">Theft/Loss</SelectItem>
<SelectItem value="found">Found Items</SelectItem>
<SelectItem value="correction">Data Correction</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
)}
</FormFieldControlled>
<FormFieldControlled
name="notes"
label="Notes"
description="Additional details about this adjustment"
>
{(field) => (
<Textarea
{...field}
placeholder="Enter any additional notes..."
rows={3}
/>
)}
</FormFieldControlled>
</div>
<div className="flex justify-end space-x-2">
<Button type="button" variant="outline" onClick={onCancel}>
Cancel
</Button>
<LoadingButton
type="submit"
loading={form.formState.isSubmitting}
variant={difference < 0 ? 'destructive' : 'default'}
>
Apply Adjustment
</LoadingButton>
</div>
</form>
</Form>
);
}
Dashboard Components
MetricCard Component
Dashboard metric display with change indicators:
interface MetricCardProps {
title: string;
value: string | number;
change?: {
value: number;
type: 'increase' | 'decrease';
period: string;
};
icon?: React.ReactNode;
color?: 'default' | 'green' | 'red' | 'blue' | 'yellow';
loading?: boolean;
}
export function MetricCard({
title,
value,
change,
icon,
color = 'default',
loading = false
}: MetricCardProps) {
if (loading) {
return (
<Card>
<CardContent className="p-6">
<div className="space-y-3">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-8 w-16" />
<Skeleton className="h-3 w-20" />
</div>
</CardContent>
</Card>
);
}
return (
<Card className="hover:shadow-md transition-shadow">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">{title}</p>
<p className="text-2xl font-bold">{value}</p>
{change && (
<div className="flex items-center gap-1">
{change.type === 'increase' ? (
<TrendingUp className="h-3 w-3 text-green-600" />
) : (
<TrendingDown className="h-3 w-3 text-red-600" />
)}
<span className={cn(
"text-xs font-medium",
change.type === 'increase' ? "text-green-600" : "text-red-600"
)}>
{change.value}% from {change.period}
</span>
</div>
)}
</div>
{icon && (
<div className={cn(
"p-3 rounded-lg",
{
'bg-gray-100 text-gray-600': color === 'default',
'bg-green-100 text-green-600': color === 'green',
'bg-red-100 text-red-600': color === 'red',
'bg-blue-100 text-blue-600': color === 'blue',
'bg-yellow-100 text-yellow-600': color === 'yellow',
}
)}>
{icon}
</div>
)}
</div>
</CardContent>
</Card>
);
}
These business logic components encapsulate Smart Shelf's core functionality, providing reusable, domain-specific UI elements that maintain consistency across the application.