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.