diff --git a/backend/src/main/java/com/rit/canteen/sales/controller/OrderController.java b/backend/src/main/java/com/rit/canteen/sales/controller/OrderController.java index 3cc7a299..3aeea19f 100644 --- a/backend/src/main/java/com/rit/canteen/sales/controller/OrderController.java +++ b/backend/src/main/java/com/rit/canteen/sales/controller/OrderController.java @@ -40,6 +40,7 @@ public class OrderController { @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate, @RequestParam(required = false) String status, @RequestParam(required = false) String paymentType, + @RequestParam(required = false) String orderType, @RequestParam(required = false) String search, @RequestParam(defaultValue = "false") boolean archived, @RequestParam(defaultValue = "0") int page, @@ -66,6 +67,9 @@ public class OrderController { if (paymentType != null && !paymentType.isEmpty()) { predicates.add(cb.equal(root.get("paymentMethod"), paymentType)); } + if (orderType != null && !orderType.isEmpty()) { + predicates.add(cb.equal(root.get("orderType"), orderType)); + } if (search != null && !search.isEmpty()) { String searchLower = "%" + search.toLowerCase() + "%"; Join userJoin = root.join("user", JoinType.LEFT); @@ -110,53 +114,61 @@ public class OrderController { @PostMapping public ResponseEntity> placeOrder(@RequestBody Order order) { - // Link items to the order for bidirectional relationship - if (order.getItems() != null) { - for (OrderItem item : order.getItems()) { - System.out.println("🛒 RECEIVED ITEM: " + item.getProductName() + " | StallID: " + item.getStallId() + " | StallName: " + item.getStallName()); - item.setOrder(order); - } - } - - // Use the actual creation time or current time for counting - LocalDateTime now = LocalDateTime.now(); - order.setCreatedAt(now); - - // Calculate start of current day to find how many orders placed today - LocalDateTime startOfDay = now.toLocalDate().atStartOfDay(); - long todaysOrderCount = orderRepository.countByCreatedAtGreaterThanEqual(startOfDay); - - // Generate formatted display ID (#001, #002...) resetting daily - String displayId = String.format("%03d", todaysOrderCount + 1); - order.setDisplayOrderId(displayId); - - // Save the complete order first - Order savedOrder = orderRepository.save(order); - - // --- Reduct Stock Logic --- - if (savedOrder.getItems() != null) { - for (OrderItem item : savedOrder.getItems()) { - Long productId = item.getProductId(); - if (productId != null) { - productRepository.findById(productId).ifPresent(product -> { - int currentStock = product.getStock() != null ? product.getStock() : 0; - product.setStock(currentStock - item.getQuantity()); - productRepository.save(product); - System.out.println("Updating Stock for " + product.getName() + ": " + currentStock + " -> " + product.getStock()); - }); + try { + // Link items to the order for bidirectional relationship + if (order.getItems() != null) { + for (OrderItem item : order.getItems()) { + System.out.println("🛒 RECEIVED ITEM: " + item.getProductName()); + item.setOrder(order); } } + + // Use the actual creation time or current time for counting + LocalDateTime now = LocalDateTime.now(); + order.setCreatedAt(now); + + // Calculate start of current day to find how many orders placed today + LocalDateTime startOfDay = now.toLocalDate().atStartOfDay(); + long todaysOrderCount = orderRepository.countByCreatedAtGreaterThanEqual(startOfDay); + + // Generate formatted display ID (#001, #002...) resetting daily + String displayId = String.format("%03d", todaysOrderCount + 1); + order.setDisplayOrderId(displayId); + + // Save the complete order first + Order savedOrder = orderRepository.save(order); + + // --- Reduct Stock Logic --- + if (savedOrder.getItems() != null) { + for (OrderItem item : savedOrder.getItems()) { + Long productId = item.getProductId(); + if (productId != null) { + productRepository.findById(productId).ifPresent(product -> { + int currentStock = product.getStock() != null ? product.getStock() : 0; + product.setStock(currentStock - item.getQuantity()); + productRepository.save(product); + System.out.println("Updating Stock for " + product.getName() + ": " + currentStock + " -> " + product.getStock()); + }); + } + } + } + + System.out.println("Placed Daily Order: " + savedOrder.getId() + " -> Display ID: #" + displayId); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("orderNumber", savedOrder.getOrderNumber()); + response.put("displayOrderId", savedOrder.getDisplayOrderId()); + response.put("message", "Order placed successfully"); + + return ResponseEntity.ok(response); + } catch (Exception e) { + e.printStackTrace(); + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("error", e.getMessage()); + return ResponseEntity.status(500).body(errorResponse); } - - System.out.println("Placed Daily Order: " + savedOrder.getId() + " -> Display ID: #" + displayId); - - Map response = new HashMap<>(); - response.put("success", true); - response.put("orderNumber", savedOrder.getOrderNumber()); // Secure ID for QR - response.put("displayOrderId", savedOrder.getDisplayOrderId()); // Sequential ID (#001) - response.put("message", "Order placed successfully"); - - return ResponseEntity.ok(response); } @GetMapping("/user/{userId}") diff --git a/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java b/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java index 89c8aa7a..8ce5f59a 100644 --- a/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java +++ b/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java @@ -16,6 +16,16 @@ public class ProductController { @Autowired private ProductRepository productRepository; + @GetMapping("/categories") + public List getAllCategories() { + return productRepository.findAll().stream() + .map(Product::getCategory) + .filter(c -> c != null && !c.isEmpty()) + .distinct() + .sorted() + .toList(); + } + @GetMapping public org.springframework.data.domain.Page getAllProducts( @RequestParam(defaultValue = "0") int page, diff --git a/backend/src/main/java/com/rit/canteen/sales/model/Order.java b/backend/src/main/java/com/rit/canteen/sales/model/Order.java index 38bf2be1..efb0dc79 100644 --- a/backend/src/main/java/com/rit/canteen/sales/model/Order.java +++ b/backend/src/main/java/com/rit/canteen/sales/model/Order.java @@ -41,6 +41,9 @@ public class Order { @Column(nullable = false) private LocalDateTime createdAt; + @Column(nullable = false) + private String orderType = "STORE_ORDER"; // STORE_ORDER, QR_ORDER, MY_ORDER, etc. + @Column(name = "is_archived", nullable = false) private boolean isArchived = false; @@ -97,6 +100,9 @@ public class Order { public boolean isArchived() { return isArchived; } public void setArchived(boolean archived) { isArchived = archived; } + public String getOrderType() { return orderType; } + public void setOrderType(String orderType) { this.orderType = orderType; } + public List getItems() { return items; } public void setItems(List items) { this.items = items; } } diff --git a/frontend/src/pages/BaseMenu.tsx b/frontend/src/pages/BaseMenu.tsx index b7ca69fd..1a7bf3b7 100644 --- a/frontend/src/pages/BaseMenu.tsx +++ b/frontend/src/pages/BaseMenu.tsx @@ -76,7 +76,8 @@ const BaseMenu = () => { setShowProductsModal(true); setProductsLoading(true); try { - const response = await fetch(`http://localhost:8080/api/products/category/${encodeURIComponent(baseItem.name)}`); + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/products/category/${encodeURIComponent(baseItem.name)}`); const data = await response.json(); setAssociatedProducts(data); } catch (error) { @@ -88,9 +89,10 @@ const BaseMenu = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + const host = window.location.hostname; const url = editingItem - ? `http://localhost:8080/api/base-items/${editingItem.id}` - : 'http://localhost:8080/api/base-items'; + ? `http://${host}:8080/api/base-items/${editingItem.id}` + : `http://${host}:8080/api/base-items`; const method = editingItem ? 'PUT' : 'POST'; try { @@ -119,7 +121,8 @@ const BaseMenu = () => { const handleToggleActive = async (item: BaseItem) => { try { - const response = await fetch(`http://localhost:8080/api/base-items/${item.id}`, { + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/base-items/${item.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...item, active: !item.active }), @@ -200,7 +203,7 @@ const BaseMenu = () => { - ) : items.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())).length === 0 ? ( + ) : items.filter(item => (item.name || '').toLowerCase().includes((searchTerm || '').toLowerCase())).length === 0 ? (

No items found

@@ -209,7 +212,7 @@ const BaseMenu = () => { ) : ( items - .filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())) + .filter(item => (item.name || '').toLowerCase().includes((searchTerm || '').toLowerCase())) .map((item) => ( #{item.id} diff --git a/frontend/src/pages/Bills.tsx b/frontend/src/pages/Bills.tsx index 17fcbd67..d71132bc 100644 --- a/frontend/src/pages/Bills.tsx +++ b/frontend/src/pages/Bills.tsx @@ -74,8 +74,9 @@ const Bills: React.FC = () => { }; const filteredOrders = orders.filter(order => { - const matchesSearch = order.purchaseId.toLowerCase().includes(searchTerm.toLowerCase()) || - order.referenceId?.toLowerCase().includes(searchTerm.toLowerCase()); + const search = (searchTerm || '').toLowerCase(); + const matchesSearch = (order.purchaseId || '').toLowerCase().includes(search) || + (order.referenceId || '').toLowerCase().includes(search); const matchesStatus = statusFilter === 'All' || order.status === statusFilter; return matchesSearch && matchesStatus; }); diff --git a/frontend/src/pages/Customers.tsx b/frontend/src/pages/Customers.tsx index 8fbfc04b..c6edf3b3 100644 --- a/frontend/src/pages/Customers.tsx +++ b/frontend/src/pages/Customers.tsx @@ -146,10 +146,12 @@ const Customers: React.FC = () => { return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2); }; - const filteredUsers = users.filter(user => - user.name?.toLowerCase().includes(searchTerm.toLowerCase()) || - user.mobileNumber?.includes(searchTerm) - ); + const filteredCustomers = customers.filter(user => { + const search = (searchTerm || '').toLowerCase(); + const name = (user.name || '').toLowerCase(); + return name.includes(search) || + (user.mobileNumber || '').includes(searchTerm); + }); return ( <> diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 2e12e5a4..06431047 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -43,13 +43,13 @@ const Login = () => { const user = await response.json(); // Strict Role Validation - if (user.role.toLowerCase() !== role) { - alert(`Access Denied: You are trying to login as ${role.toUpperCase()}, but your credentials belong to a ${user.role.toUpperCase()} account.`); + if ((user.role || '').toLowerCase() !== (role || '').toLowerCase()) { + alert(`Access Denied: You are trying to login as ${(role || '').toUpperCase()}, but your credentials belong to a ${(user.role || '').toUpperCase()} account.`); return; } sessionStorage.setItem('isLoggedIn', 'true'); - sessionStorage.setItem('userRole', user.role.toLowerCase()); + sessionStorage.setItem('userRole', (user.role || '').toLowerCase()); sessionStorage.setItem('userPermissions', JSON.stringify(user.permissions || [])); navigate('/store-dashboard'); } else { diff --git a/frontend/src/pages/Managers.tsx b/frontend/src/pages/Managers.tsx index d6731beb..c5feff73 100644 --- a/frontend/src/pages/Managers.tsx +++ b/frontend/src/pages/Managers.tsx @@ -193,7 +193,11 @@ const Managers = () => { - {managers.filter(m => m.name.toLowerCase().includes(searchQuery.toLowerCase()) || m.email.toLowerCase().includes(searchQuery.toLowerCase())).map((manager) => ( + {managers.filter(m => { + const query = (searchQuery || '').toLowerCase(); + return (m.name || '').toLowerCase().includes(query) || + (m.email || '').toLowerCase().includes(query); + }).map((manager) => (
diff --git a/frontend/src/pages/Orders.tsx b/frontend/src/pages/Orders.tsx index 795e3a14..d7012abe 100644 --- a/frontend/src/pages/Orders.tsx +++ b/frontend/src/pages/Orders.tsx @@ -82,10 +82,12 @@ const Orders: React.FC = () => { const fetchProducts = async () => { try { - const response = await fetch(`http://${window.location.hostname}:8080/api/products`); + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/products`); if (response.ok) { const data = await response.json(); - setAllProducts(data); + // Handle both Array and Page object responses for robustness + setAllProducts(Array.isArray(data) ? data : (data.content || [])); } } catch (error) { console.error('Error fetching products:', error); @@ -233,10 +235,11 @@ const Orders: React.FC = () => { }; const filteredProducts = useMemo(() => { - if (!editSearchQuery.trim()) return []; + const query = (editSearchQuery || '').trim().toLowerCase(); + if (!query) return []; return allProducts.filter(p => - p.name.toLowerCase().includes(editSearchQuery.toLowerCase()) || - p.category.toLowerCase().includes(editSearchQuery.toLowerCase()) + (p.name || '').toLowerCase().includes(query) || + (p.category || '').toLowerCase().includes(query) ).slice(0, 5); // Limit results }, [allProducts, editSearchQuery]); @@ -246,7 +249,7 @@ const Orders: React.FC = () => { }; const getStatusColor = (status: string) => { - switch (status.toUpperCase()) { + switch ((status || '').toUpperCase()) { case 'COMPLETED': return 'bg-emerald-50 text-emerald-600 border-emerald-100'; case 'PAID': return 'bg-indigo-50 text-indigo-600 border-indigo-100'; case 'PENDING': return 'bg-amber-50 text-amber-600 border-amber-100'; diff --git a/frontend/src/pages/Products.tsx b/frontend/src/pages/Products.tsx index 5f9ce1ef..e124f893 100644 --- a/frontend/src/pages/Products.tsx +++ b/frontend/src/pages/Products.tsx @@ -117,15 +117,21 @@ const Products = () => { const host = window.location.hostname; const response = await fetch(`http://${host}:8080/api/products?page=${currentPage}&size=${pageSize}`); const data = await response.json(); + if (data && data.content) { setProducts(data.content); setTotalElements(data.totalElements); + } else if (Array.isArray(data)) { + setProducts(data); + setTotalElements(data.length); } else { setProducts([]); setTotalElements(0); } } catch (error) { console.error('Error fetching products:', error); + setProducts([]); + setTotalElements(0); } finally { setLoading(false); } @@ -133,17 +139,26 @@ const Products = () => { const fetchBaseItems = async () => { try { - const response = await fetch('http://localhost:8080/api/base-items'); + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/base-items`); const data = await response.json(); - setBaseItems(data); + if (data && data.content) { + setBaseItems(data.content); + } else if (Array.isArray(data)) { + setBaseItems(data); + } else { + setBaseItems([]); + } } catch (error) { console.error('Error fetching base items:', error); + setBaseItems([]); } }; const fetchAllStalls = async () => { try { - const response = await fetch('http://localhost:8080/api/stalls'); + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/stalls`); const data = await response.json(); setAllStalls(data); } catch (error) { @@ -153,9 +168,10 @@ const Products = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + const host = window.location.hostname; const url = editingProduct - ? `http://localhost:8080/api/products/${editingProduct.id}` - : 'http://localhost:8080/api/products'; + ? `http://${host}:8080/api/products/${editingProduct.id}` + : `http://${host}:8080/api/products`; const method = editingProduct ? 'PUT' : 'POST'; try { @@ -168,6 +184,7 @@ const Products = () => { setShowModal(false); setEditingProduct(null); setFormData(emptyProduct); + setSearchTerm(''); // Clear search term to ensure updated product is visible fetchProducts(); } } catch (error) { @@ -187,7 +204,8 @@ const Products = () => { const handleToggleActive = async (product: Product) => { try { - const response = await fetch(`http://localhost:8080/api/products/${product.id}`, { + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/products/${product.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...product, active: !product.active }), @@ -203,7 +221,8 @@ const Products = () => { const handleToggleStock = async (product: Product) => { try { - const response = await fetch(`http://localhost:8080/api/products/${product.id}/toggle-stock`, { + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/products/${product.id}/toggle-stock`, { method: 'PATCH', }); if (response.ok) { @@ -218,7 +237,8 @@ const Products = () => { const handleDelete = async (product: Product) => { if (!window.confirm(`Are you sure you want to delete ${product.name}?`)) return; try { - const response = await fetch(`http://localhost:8080/api/products/${product.id}`, { + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/products/${product.id}`, { method: 'DELETE', }); if (response.ok) { @@ -303,9 +323,21 @@ const Products = () => { {loading ? ( Loading... - ) : products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()) || p.category.toLowerCase().includes(searchTerm.toLowerCase())).length === 0 ? ( + ) : products.filter(p => { + if (!p) return false; + const search = (searchTerm || '').toLowerCase(); + const name = (p.name || '').toLowerCase(); + const category = (p.category || '').toLowerCase(); + return name.includes(search) || category.includes(search); + }).length === 0 ? ( No products found - ) : products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()) || p.category.toLowerCase().includes(searchTerm.toLowerCase())).map((product) => ( + ) : products.filter(p => { + if (!p) return false; + const search = (searchTerm || '').toLowerCase(); + const name = (p.name || '').toLowerCase(); + const category = (p.category || '').toLowerCase(); + return name.includes(search) || category.includes(search); + }).map((product) => (
@@ -327,9 +359,9 @@ const Products = () => {
{(() => { // Combine direct stalls with stalls that have this product's category in their baseItems - const directStalls = product.stalls || []; - const indirectStalls = allStalls.filter(s => - s.baseItems?.some(bi => bi.name.toLowerCase() === product.category?.toLowerCase()) + const directStalls = (product && product.stalls) || []; + const indirectStalls = (allStalls || []).filter(s => + s.baseItems?.some(bi => bi.name?.toLowerCase() === product.category?.toLowerCase()) ).map(s => ({ id: s.id, name: s.name })); // Unique stalls by ID diff --git a/frontend/src/pages/PurchaseAnalytics.tsx b/frontend/src/pages/PurchaseAnalytics.tsx index 8d788de3..f0518313 100644 --- a/frontend/src/pages/PurchaseAnalytics.tsx +++ b/frontend/src/pages/PurchaseAnalytics.tsx @@ -161,7 +161,7 @@ const PurchaseAnalytics = () => { }; const filteredAnalytics = analytics.filter(item => { - const matchesSearch = item.product.toLowerCase().includes(searchTerm.toLowerCase()); + const matchesSearch = (item.product || '').toLowerCase().includes((searchTerm || '').toLowerCase()); const matchesFilter = filterType === 'all' || item.trend === filterType; return matchesSearch && matchesFilter; }); diff --git a/frontend/src/pages/Purchases.tsx b/frontend/src/pages/Purchases.tsx index 3c54002a..c99c8a62 100644 --- a/frontend/src/pages/Purchases.tsx +++ b/frontend/src/pages/Purchases.tsx @@ -258,7 +258,7 @@ const Purchases: React.FC = () => { Loading records... - ) : orders.filter(o => o.purchaseId.toLowerCase().includes(searchTerm.toLowerCase())).map((order) => ( + ) : orders.filter(o => (o.purchaseId || '').toLowerCase().includes((searchTerm || '').toLowerCase())).map((order) => ( {order.purchaseId} {new Date(order.date).toLocaleDateString()} diff --git a/frontend/src/pages/Staff.tsx b/frontend/src/pages/Staff.tsx index de12957e..9617521c 100644 --- a/frontend/src/pages/Staff.tsx +++ b/frontend/src/pages/Staff.tsx @@ -109,10 +109,11 @@ const Staff = () => { } }; - const filteredStaff = staffList.filter(s => - s.name.toLowerCase().includes(searchQuery.toLowerCase()) || - s.email.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const filteredStaff = staff.filter(s => { + const query = (searchQuery || '').toLowerCase(); + return (s.name || '').toLowerCase().includes(query) || + (s.email || '').toLowerCase().includes(query); + }); return (
diff --git a/frontend/src/pages/Stalls.tsx b/frontend/src/pages/Stalls.tsx index 085faeef..2e35781b 100644 --- a/frontend/src/pages/Stalls.tsx +++ b/frontend/src/pages/Stalls.tsx @@ -198,8 +198,15 @@ const Stalls: React.FC = () => { fetch(`http://${host}:8080/api/base-items`) ]); - if (prodRes.ok) setAllProducts(await prodRes.json()); - if (baseRes.ok) setAllBaseItems(await baseRes.json()); + if (prodRes.ok) { + const data = await prodRes.json(); + // Handle both Array and Page object responses for robustness + setAllProducts(Array.isArray(data) ? data : (data.content || [])); + } + if (baseRes.ok) { + const data = await baseRes.json(); + setAllBaseItems(Array.isArray(data) ? data : (data.content || [])); + } } catch (error) { console.error('Error fetching available items:', error); } @@ -234,10 +241,11 @@ const Stalls: React.FC = () => { } }; - const filteredStalls = stalls.filter(s => - s.name.toLowerCase().includes(searchQuery.toLowerCase()) || - s.description.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const filteredStalls = stalls.filter(s => { + const query = (searchQuery || '').toLowerCase(); + return (s.name || '').toLowerCase().includes(query) || + (s.description || '').toLowerCase().includes(query); + }); const isStallOpen = (stall: Stall) => { if (stall.temporarilyClosed) return false; @@ -618,7 +626,7 @@ const Stalls: React.FC = () => {
{activeTab === 'products' ? ( allProducts - .filter(p => p.name.toLowerCase().includes(itemSearchQuery.toLowerCase())) + .filter(p => (p.name || '').toLowerCase().includes((itemSearchQuery || '').toLowerCase())) .map(product => { const isSelected = tempProductIds.includes(product.id); return ( @@ -644,7 +652,7 @@ const Stalls: React.FC = () => { }) ) : ( allBaseItems - .filter(b => b.name.toLowerCase().includes(itemSearchQuery.toLowerCase())) + .filter(b => (b.name || '').toLowerCase().includes((itemSearchQuery || '').toLowerCase())) .map(baseItem => { const isSelected = tempBaseItemIds.includes(baseItem.id); return ( diff --git a/frontend/src/pages/Terminals.tsx b/frontend/src/pages/Terminals.tsx index 09592c74..a6a327bb 100644 --- a/frontend/src/pages/Terminals.tsx +++ b/frontend/src/pages/Terminals.tsx @@ -60,11 +60,11 @@ const Terminals = () => { } }; - const filteredTerminals = terminals.filter(t => - t.name.toLowerCase().includes(searchQuery.toLowerCase()) || - t.location.toLowerCase().includes(searchQuery.toLowerCase()) - ); - + const filteredTerminals = terminals.filter(t => { + const query = (searchQuery || '').toLowerCase(); + return (t.name || '').toLowerCase().includes(query) || + (t.location || '').toLowerCase().includes(query); + }); return (
{/* Header Section */} diff --git a/frontend/src/pages/Vendors.tsx b/frontend/src/pages/Vendors.tsx index 0547bca5..bbab4642 100644 --- a/frontend/src/pages/Vendors.tsx +++ b/frontend/src/pages/Vendors.tsx @@ -134,10 +134,13 @@ const Vendors: React.FC = () => { } }; - const filteredVendors = vendors.filter(v => - v.name?.toLowerCase().includes(searchTerm.toLowerCase()) || - v.companyName?.toLowerCase().includes(searchTerm.toLowerCase()) - ); + const filteredVendors = vendors.filter(v => { + if (!v) return false; + const search = (searchTerm || '').toLowerCase(); + const name = (v.name || '').toLowerCase(); + const company = (v.companyName || '').toLowerCase(); + return name.includes(search) || company.includes(search); + }); return ( <>