Search dropdown added in the Purchase screen of Vendor Dashboard.

This commit is contained in:
Sidharth Prabhu
2026-04-20 10:53:20 +05:30
parent e58baba908
commit 149c579df6

View File

@@ -23,7 +23,9 @@ interface PurchaseOrder {
const Purchases: React.FC = () => { const Purchases: React.FC = () => {
const [orders, setOrders] = useState<PurchaseOrder[]>([]); const [orders, setOrders] = useState<PurchaseOrder[]>([]);
const [vendors, setVendors] = useState<{id: number, name: string}[]>([]); const [vendors, setVendors] = useState<{id: number, name: string}[]>([]);
const [availableProducts, setAvailableProducts] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [activeSearchIdx, setActiveSearchIdx] = useState<number | null>(null);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [showAddModal, setShowAddModal] = useState(false); const [showAddModal, setShowAddModal] = useState(false);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
@@ -49,6 +51,15 @@ const Purchases: React.FC = () => {
useEffect(() => { useEffect(() => {
fetchOrders(); fetchOrders();
fetchVendors(); fetchVendors();
fetchProducts();
const handleClickOutside = (e: MouseEvent) => {
if (!(e.target as HTMLElement).closest('.product-search-container')) {
setActiveSearchIdx(null);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []); }, []);
const fetchOrders = async () => { const fetchOrders = async () => {
@@ -78,6 +89,18 @@ const Purchases: React.FC = () => {
} }
}; };
const fetchProducts = async () => {
try {
const response = await fetch('/api/products?size=1000');
if (response.ok) {
const data = await response.json();
setAvailableProducts(data.content || data);
}
} catch (error) {
console.error('Error fetching products:', error);
}
};
const handleAddItem = () => { const handleAddItem = () => {
setNewOrder({ setNewOrder({
...newOrder, ...newOrder,
@@ -383,7 +406,7 @@ const Purchases: React.FC = () => {
</button> </button>
</div> </div>
<div className="border border-[#e2e8f0] rounded-2xl overflow-hidden"> <div className="border border-[#e2e8f0] rounded-2xl">
<table className="w-full text-left"> <table className="w-full text-left">
<thead className="bg-gray-50 border-b border-[#e2e8f0]"> <thead className="bg-gray-50 border-b border-[#e2e8f0]">
<tr> <tr>
@@ -397,8 +420,58 @@ const Purchases: React.FC = () => {
<tbody className="divide-y divide-[#e2e8f0]"> <tbody className="divide-y divide-[#e2e8f0]">
{newOrder.items.map((item, idx) => ( {newOrder.items.map((item, idx) => (
<tr key={idx}> <tr key={idx}>
<td className="px-4 py-3"> <td className="px-4 py-3 relative product-search-container">
<input type="text" placeholder="Product name..." value={item.productName} onChange={e => handleItemChange(idx, 'productName', e.target.value)} className="w-full bg-transparent text-sm font-bold outline-none" /> <input
type="text"
placeholder="Search or type product..."
value={item.productName}
onChange={e => handleItemChange(idx, 'productName', e.target.value)}
onFocus={() => setActiveSearchIdx(idx)}
className="w-full bg-transparent text-sm font-bold outline-none"
/>
{activeSearchIdx === idx && (
<div className="absolute left-0 right-0 top-full mt-1 bg-white border border-[#e2e8f0] rounded-xl shadow-2xl z-[150] max-h-60 overflow-y-auto animate-in fade-in slide-in-from-top-2">
{availableProducts
.filter(p => {
const name = p.name || '';
const query = (item.productName || '').toLowerCase();
return name.toLowerCase().includes(query);
})
.slice(0, 10)
.map(p => (
<button
key={p.id}
type="button"
onClick={() => {
handleItemChange(idx, 'productName', p.name);
handleItemChange(idx, 'rate', p.basePrice || 0);
setActiveSearchIdx(null);
}}
className="w-full text-left px-4 py-3 hover:bg-indigo-50 flex flex-col transition-colors border-b border-[#f1f5f9] last:border-0"
>
<span className="text-sm font-bold text-[#1e293b]">{p.name}</span>
<div className="flex items-center gap-2 mt-0.5">
<span className="text-[10px] bg-slate-100 text-slate-500 px-1.5 py-0.5 rounded uppercase font-black tracking-tighter">{p.category || 'No Category'}</span>
<span className="text-[10px] font-bold text-[#231651]">Cost: {p.basePrice || 0}</span>
</div>
</button>
))
}
{item.productName && !availableProducts.some(p => p.name.toLowerCase() === item.productName.toLowerCase()) && (
<button
type="button"
onClick={() => setActiveSearchIdx(null)}
className="w-full text-left px-4 py-3 hover:bg-amber-50 flex items-center gap-3 transition-colors text-amber-600"
>
<Plus size={16} />
<span className="text-sm font-bold">Order "{item.productName}" as new item</span>
</button>
)}
{availableProducts.length === 0 && (
<div className="px-4 py-3 text-xs text-[#64748b] italic">No existing products found</div>
)}
</div>
)}
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<input type="number" value={item.quantity} onChange={e => handleItemChange(idx, 'quantity', parseFloat(e.target.value))} className="w-full bg-transparent text-sm font-bold outline-none" /> <input type="number" value={item.quantity} onChange={e => handleItemChange(idx, 'quantity', parseFloat(e.target.value))} className="w-full bg-transparent text-sm font-bold outline-none" />