Files
ember-market-frontend/components/modals/product-selector.tsx
g 07dcaf55c0 Refactor API calls to use apiRequest instead of clientFetch
Replaces all usages of clientFetch with the new apiRequest utility across dashboard pages, modal components, and the profit analytics service. This standardizes API interaction and improves consistency in request handling.
2025-12-12 20:05:26 +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 { apiRequest } 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 apiRequest('/products/for-selection', 'GET');
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>
);
}