Refactored all API calls to use the new clientFetch utility instead of apiRequest in dashboard pages, modal components, and profit analytics service. This improves consistency and aligns with updated API handling patterns.
130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import { Search, Package } from "lucide-react";
|
|
import { clientFetch } from "@/lib/api";
|
|
|
|
interface Product {
|
|
_id: string;
|
|
name: string;
|
|
description?: string;
|
|
unitType: string;
|
|
pricing: Array<{ minQuantity: number; pricePerUnit: number }>;
|
|
image?: string;
|
|
}
|
|
|
|
interface ProductSelectorProps {
|
|
selectedProducts: string[];
|
|
onSelectionChange: (productIds: string[]) => void;
|
|
}
|
|
|
|
export default function ProductSelector({ selectedProducts, onSelectionChange }: ProductSelectorProps) {
|
|
const [products, setProducts] = useState<Product[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
|
|
useEffect(() => {
|
|
const fetchProducts = async () => {
|
|
try {
|
|
const fetchedProducts = await clientFetch('/products/for-selection');
|
|
setProducts(fetchedProducts);
|
|
} catch (error) {
|
|
console.error('Error fetching products:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchProducts();
|
|
}, []);
|
|
|
|
const filteredProducts = products.filter(product =>
|
|
product.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
);
|
|
|
|
const handleProductToggle = (productId: string) => {
|
|
const newSelection = selectedProducts.includes(productId)
|
|
? selectedProducts.filter(id => id !== productId)
|
|
: [...selectedProducts, productId];
|
|
onSelectionChange(newSelection);
|
|
};
|
|
|
|
const getMinPrice = (product: Product) => {
|
|
if (!product.pricing || product.pricing.length === 0) return 0;
|
|
const minTier = product.pricing.reduce((min, tier) =>
|
|
tier.pricePerUnit < min.pricePerUnit ? tier : min
|
|
);
|
|
return minTier.pricePerUnit;
|
|
};
|
|
|
|
if (loading) {
|
|
return <div className="text-center py-4">Loading products...</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="relative">
|
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
<Input
|
|
placeholder="Search products..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
|
|
<ScrollArea className="h-48">
|
|
<div className="space-y-2">
|
|
{filteredProducts.length === 0 ? (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
<Package className="h-8 w-8 mx-auto mb-2" />
|
|
{searchTerm ? "No products found" : "No products available"}
|
|
</div>
|
|
) : (
|
|
filteredProducts.map((product) => (
|
|
<div
|
|
key={product._id}
|
|
className="flex items-start space-x-3 p-3 border rounded-lg hover:bg-muted/50"
|
|
>
|
|
<Checkbox
|
|
checked={selectedProducts.includes(product._id)}
|
|
onCheckedChange={() => handleProductToggle(product._id)}
|
|
className="mt-0.5"
|
|
/>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-start justify-between gap-2">
|
|
<div className="flex-1 min-w-0">
|
|
<p className="font-medium text-sm leading-tight mb-1">
|
|
{product.name}
|
|
</p>
|
|
{product.description && (
|
|
<p className="text-xs text-muted-foreground leading-tight line-clamp-2">
|
|
{product.description.length > 80
|
|
? `${product.description.substring(0, 80)}...`
|
|
: product.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="text-sm font-medium text-green-600 dark:text-green-400 flex-shrink-0">
|
|
£{getMinPrice(product).toFixed(2)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</ScrollArea>
|
|
|
|
{selectedProducts.length > 0 && (
|
|
<div className="text-sm text-muted-foreground">
|
|
{selectedProducts.length} product(s) selected
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|