Files
ember-market-frontend/components/modals/product-selector.tsx
g fd5440c4da Replace apiRequest with clientFetch across app
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.
2025-12-11 19:52:43 +00:00

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>
);
}