Integrated stock reduction logic and branding updates

This commit is contained in:
2026-04-16 09:11:49 +05:30
parent 3d6a57593d
commit 54172f6dff
16 changed files with 187 additions and 102 deletions

View File

@@ -40,6 +40,7 @@ public class OrderController {
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate, @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate,
@RequestParam(required = false) String status, @RequestParam(required = false) String status,
@RequestParam(required = false) String paymentType, @RequestParam(required = false) String paymentType,
@RequestParam(required = false) String orderType,
@RequestParam(required = false) String search, @RequestParam(required = false) String search,
@RequestParam(defaultValue = "false") boolean archived, @RequestParam(defaultValue = "false") boolean archived,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "0") int page,
@@ -66,6 +67,9 @@ public class OrderController {
if (paymentType != null && !paymentType.isEmpty()) { if (paymentType != null && !paymentType.isEmpty()) {
predicates.add(cb.equal(root.get("paymentMethod"), paymentType)); 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()) { if (search != null && !search.isEmpty()) {
String searchLower = "%" + search.toLowerCase() + "%"; String searchLower = "%" + search.toLowerCase() + "%";
Join<Order, User> userJoin = root.join("user", JoinType.LEFT); Join<Order, User> userJoin = root.join("user", JoinType.LEFT);
@@ -110,10 +114,11 @@ public class OrderController {
@PostMapping @PostMapping
public ResponseEntity<Map<String, Object>> placeOrder(@RequestBody Order order) { public ResponseEntity<Map<String, Object>> placeOrder(@RequestBody Order order) {
try {
// Link items to the order for bidirectional relationship // Link items to the order for bidirectional relationship
if (order.getItems() != null) { if (order.getItems() != null) {
for (OrderItem item : order.getItems()) { for (OrderItem item : order.getItems()) {
System.out.println("🛒 RECEIVED ITEM: " + item.getProductName() + " | StallID: " + item.getStallId() + " | StallName: " + item.getStallName()); System.out.println("🛒 RECEIVED ITEM: " + item.getProductName());
item.setOrder(order); item.setOrder(order);
} }
} }
@@ -152,11 +157,18 @@ public class OrderController {
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
response.put("success", true); response.put("success", true);
response.put("orderNumber", savedOrder.getOrderNumber()); // Secure ID for QR response.put("orderNumber", savedOrder.getOrderNumber());
response.put("displayOrderId", savedOrder.getDisplayOrderId()); // Sequential ID (#001) response.put("displayOrderId", savedOrder.getDisplayOrderId());
response.put("message", "Order placed successfully"); response.put("message", "Order placed successfully");
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} catch (Exception e) {
e.printStackTrace();
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("success", false);
errorResponse.put("error", e.getMessage());
return ResponseEntity.status(500).body(errorResponse);
}
} }
@GetMapping("/user/{userId}") @GetMapping("/user/{userId}")

View File

@@ -16,6 +16,16 @@ public class ProductController {
@Autowired @Autowired
private ProductRepository productRepository; private ProductRepository productRepository;
@GetMapping("/categories")
public List<String> getAllCategories() {
return productRepository.findAll().stream()
.map(Product::getCategory)
.filter(c -> c != null && !c.isEmpty())
.distinct()
.sorted()
.toList();
}
@GetMapping @GetMapping
public org.springframework.data.domain.Page<Product> getAllProducts( public org.springframework.data.domain.Page<Product> getAllProducts(
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "0") int page,

View File

@@ -41,6 +41,9 @@ public class Order {
@Column(nullable = false) @Column(nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
@Column(nullable = false)
private String orderType = "STORE_ORDER"; // STORE_ORDER, QR_ORDER, MY_ORDER, etc.
@Column(name = "is_archived", nullable = false) @Column(name = "is_archived", nullable = false)
private boolean isArchived = false; private boolean isArchived = false;
@@ -97,6 +100,9 @@ public class Order {
public boolean isArchived() { return isArchived; } public boolean isArchived() { return isArchived; }
public void setArchived(boolean archived) { isArchived = archived; } public void setArchived(boolean archived) { isArchived = archived; }
public String getOrderType() { return orderType; }
public void setOrderType(String orderType) { this.orderType = orderType; }
public List<OrderItem> getItems() { return items; } public List<OrderItem> getItems() { return items; }
public void setItems(List<OrderItem> items) { this.items = items; } public void setItems(List<OrderItem> items) { this.items = items; }
} }

View File

@@ -76,7 +76,8 @@ const BaseMenu = () => {
setShowProductsModal(true); setShowProductsModal(true);
setProductsLoading(true); setProductsLoading(true);
try { 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(); const data = await response.json();
setAssociatedProducts(data); setAssociatedProducts(data);
} catch (error) { } catch (error) {
@@ -88,9 +89,10 @@ const BaseMenu = () => {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const host = window.location.hostname;
const url = editingItem const url = editingItem
? `http://localhost:8080/api/base-items/${editingItem.id}` ? `http://${host}:8080/api/base-items/${editingItem.id}`
: 'http://localhost:8080/api/base-items'; : `http://${host}:8080/api/base-items`;
const method = editingItem ? 'PUT' : 'POST'; const method = editingItem ? 'PUT' : 'POST';
try { try {
@@ -119,7 +121,8 @@ const BaseMenu = () => {
const handleToggleActive = async (item: BaseItem) => { const handleToggleActive = async (item: BaseItem) => {
try { 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', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...item, active: !item.active }), body: JSON.stringify({ ...item, active: !item.active }),
@@ -200,7 +203,7 @@ const BaseMenu = () => {
</div> </div>
</td> </td>
</tr> </tr>
) : items.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())).length === 0 ? ( ) : items.filter(item => (item.name || '').toLowerCase().includes((searchTerm || '').toLowerCase())).length === 0 ? (
<tr> <tr>
<td colSpan={6} className="px-6 py-12 text-center text-[#64748b]"> <td colSpan={6} className="px-6 py-12 text-center text-[#64748b]">
<p className="text-lg font-medium mb-1">No items found</p> <p className="text-lg font-medium mb-1">No items found</p>
@@ -209,7 +212,7 @@ const BaseMenu = () => {
</tr> </tr>
) : ( ) : (
items items
.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())) .filter(item => (item.name || '').toLowerCase().includes((searchTerm || '').toLowerCase()))
.map((item) => ( .map((item) => (
<tr key={item.id} className="hover:bg-gray-50/50 transition-all group"> <tr key={item.id} className="hover:bg-gray-50/50 transition-all group">
<td className="px-6 py-4 text-sm font-medium text-[#64748b]">#{item.id}</td> <td className="px-6 py-4 text-sm font-medium text-[#64748b]">#{item.id}</td>

View File

@@ -74,8 +74,9 @@ const Bills: React.FC = () => {
}; };
const filteredOrders = orders.filter(order => { const filteredOrders = orders.filter(order => {
const matchesSearch = order.purchaseId.toLowerCase().includes(searchTerm.toLowerCase()) || const search = (searchTerm || '').toLowerCase();
order.referenceId?.toLowerCase().includes(searchTerm.toLowerCase()); const matchesSearch = (order.purchaseId || '').toLowerCase().includes(search) ||
(order.referenceId || '').toLowerCase().includes(search);
const matchesStatus = statusFilter === 'All' || order.status === statusFilter; const matchesStatus = statusFilter === 'All' || order.status === statusFilter;
return matchesSearch && matchesStatus; return matchesSearch && matchesStatus;
}); });

View File

@@ -146,10 +146,12 @@ const Customers: React.FC = () => {
return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2); return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
}; };
const filteredUsers = users.filter(user => const filteredCustomers = customers.filter(user => {
user.name?.toLowerCase().includes(searchTerm.toLowerCase()) || const search = (searchTerm || '').toLowerCase();
user.mobileNumber?.includes(searchTerm) const name = (user.name || '').toLowerCase();
); return name.includes(search) ||
(user.mobileNumber || '').includes(searchTerm);
});
return ( return (
<> <>

View File

@@ -43,13 +43,13 @@ const Login = () => {
const user = await response.json(); const user = await response.json();
// Strict Role Validation // Strict Role Validation
if (user.role.toLowerCase() !== role) { 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.`); alert(`Access Denied: You are trying to login as ${(role || '').toUpperCase()}, but your credentials belong to a ${(user.role || '').toUpperCase()} account.`);
return; return;
} }
sessionStorage.setItem('isLoggedIn', 'true'); sessionStorage.setItem('isLoggedIn', 'true');
sessionStorage.setItem('userRole', user.role.toLowerCase()); sessionStorage.setItem('userRole', (user.role || '').toLowerCase());
sessionStorage.setItem('userPermissions', JSON.stringify(user.permissions || [])); sessionStorage.setItem('userPermissions', JSON.stringify(user.permissions || []));
navigate('/store-dashboard'); navigate('/store-dashboard');
} else { } else {

View File

@@ -193,7 +193,11 @@ const Managers = () => {
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-[#e2e8f0]"> <tbody className="divide-y divide-[#e2e8f0]">
{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) => (
<tr key={manager.id} className="hover:bg-gray-50/50 transition-colors"> <tr key={manager.id} className="hover:bg-gray-50/50 transition-colors">
<td className="px-6 py-5"> <td className="px-6 py-5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">

View File

@@ -82,10 +82,12 @@ const Orders: React.FC = () => {
const fetchProducts = async () => { const fetchProducts = async () => {
try { 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) { if (response.ok) {
const data = await response.json(); 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) { } catch (error) {
console.error('Error fetching products:', error); console.error('Error fetching products:', error);
@@ -233,10 +235,11 @@ const Orders: React.FC = () => {
}; };
const filteredProducts = useMemo(() => { const filteredProducts = useMemo(() => {
if (!editSearchQuery.trim()) return []; const query = (editSearchQuery || '').trim().toLowerCase();
if (!query) return [];
return allProducts.filter(p => return allProducts.filter(p =>
p.name.toLowerCase().includes(editSearchQuery.toLowerCase()) || (p.name || '').toLowerCase().includes(query) ||
p.category.toLowerCase().includes(editSearchQuery.toLowerCase()) (p.category || '').toLowerCase().includes(query)
).slice(0, 5); // Limit results ).slice(0, 5); // Limit results
}, [allProducts, editSearchQuery]); }, [allProducts, editSearchQuery]);
@@ -246,7 +249,7 @@ const Orders: React.FC = () => {
}; };
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
switch (status.toUpperCase()) { switch ((status || '').toUpperCase()) {
case 'COMPLETED': return 'bg-emerald-50 text-emerald-600 border-emerald-100'; 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 'PAID': return 'bg-indigo-50 text-indigo-600 border-indigo-100';
case 'PENDING': return 'bg-amber-50 text-amber-600 border-amber-100'; case 'PENDING': return 'bg-amber-50 text-amber-600 border-amber-100';

View File

@@ -117,15 +117,21 @@ const Products = () => {
const host = window.location.hostname; const host = window.location.hostname;
const response = await fetch(`http://${host}:8080/api/products?page=${currentPage}&size=${pageSize}`); const response = await fetch(`http://${host}:8080/api/products?page=${currentPage}&size=${pageSize}`);
const data = await response.json(); const data = await response.json();
if (data && data.content) { if (data && data.content) {
setProducts(data.content); setProducts(data.content);
setTotalElements(data.totalElements); setTotalElements(data.totalElements);
} else if (Array.isArray(data)) {
setProducts(data);
setTotalElements(data.length);
} else { } else {
setProducts([]); setProducts([]);
setTotalElements(0); setTotalElements(0);
} }
} catch (error) { } catch (error) {
console.error('Error fetching products:', error); console.error('Error fetching products:', error);
setProducts([]);
setTotalElements(0);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -133,17 +139,26 @@ const Products = () => {
const fetchBaseItems = async () => { const fetchBaseItems = async () => {
try { 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(); const data = await response.json();
if (data && data.content) {
setBaseItems(data.content);
} else if (Array.isArray(data)) {
setBaseItems(data); setBaseItems(data);
} else {
setBaseItems([]);
}
} catch (error) { } catch (error) {
console.error('Error fetching base items:', error); console.error('Error fetching base items:', error);
setBaseItems([]);
} }
}; };
const fetchAllStalls = async () => { const fetchAllStalls = async () => {
try { 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(); const data = await response.json();
setAllStalls(data); setAllStalls(data);
} catch (error) { } catch (error) {
@@ -153,9 +168,10 @@ const Products = () => {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const host = window.location.hostname;
const url = editingProduct const url = editingProduct
? `http://localhost:8080/api/products/${editingProduct.id}` ? `http://${host}:8080/api/products/${editingProduct.id}`
: 'http://localhost:8080/api/products'; : `http://${host}:8080/api/products`;
const method = editingProduct ? 'PUT' : 'POST'; const method = editingProduct ? 'PUT' : 'POST';
try { try {
@@ -168,6 +184,7 @@ const Products = () => {
setShowModal(false); setShowModal(false);
setEditingProduct(null); setEditingProduct(null);
setFormData(emptyProduct); setFormData(emptyProduct);
setSearchTerm(''); // Clear search term to ensure updated product is visible
fetchProducts(); fetchProducts();
} }
} catch (error) { } catch (error) {
@@ -187,7 +204,8 @@ const Products = () => {
const handleToggleActive = async (product: Product) => { const handleToggleActive = async (product: Product) => {
try { 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', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...product, active: !product.active }), body: JSON.stringify({ ...product, active: !product.active }),
@@ -203,7 +221,8 @@ const Products = () => {
const handleToggleStock = async (product: Product) => { const handleToggleStock = async (product: Product) => {
try { 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', method: 'PATCH',
}); });
if (response.ok) { if (response.ok) {
@@ -218,7 +237,8 @@ const Products = () => {
const handleDelete = async (product: Product) => { const handleDelete = async (product: Product) => {
if (!window.confirm(`Are you sure you want to delete ${product.name}?`)) return; if (!window.confirm(`Are you sure you want to delete ${product.name}?`)) return;
try { 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', method: 'DELETE',
}); });
if (response.ok) { if (response.ok) {
@@ -303,9 +323,21 @@ const Products = () => {
<tbody className="divide-y divide-[#e2e8f0]"> <tbody className="divide-y divide-[#e2e8f0]">
{loading ? ( {loading ? (
<tr><td colSpan={6} className="px-6 py-12 text-center text-[#64748b]"><RefreshCw className="animate-spin inline mr-2" />Loading...</td></tr> <tr><td colSpan={6} className="px-6 py-12 text-center text-[#64748b]"><RefreshCw className="animate-spin inline mr-2" />Loading...</td></tr>
) : 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 ? (
<tr><td colSpan={6} className="px-6 py-12 text-center text-[#64748b]">No products found</td></tr> <tr><td colSpan={6} className="px-6 py-12 text-center text-[#64748b]">No products found</td></tr>
) : 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) => (
<tr key={product.id} className="hover:bg-gray-50/50 transition-all"> <tr key={product.id} className="hover:bg-gray-50/50 transition-all">
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -327,9 +359,9 @@ const Products = () => {
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{(() => { {(() => {
// Combine direct stalls with stalls that have this product's category in their baseItems // Combine direct stalls with stalls that have this product's category in their baseItems
const directStalls = product.stalls || []; const directStalls = (product && product.stalls) || [];
const indirectStalls = allStalls.filter(s => const indirectStalls = (allStalls || []).filter(s =>
s.baseItems?.some(bi => bi.name.toLowerCase() === product.category?.toLowerCase()) s.baseItems?.some(bi => bi.name?.toLowerCase() === product.category?.toLowerCase())
).map(s => ({ id: s.id, name: s.name })); ).map(s => ({ id: s.id, name: s.name }));
// Unique stalls by ID // Unique stalls by ID

View File

@@ -161,7 +161,7 @@ const PurchaseAnalytics = () => {
}; };
const filteredAnalytics = analytics.filter(item => { 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; const matchesFilter = filterType === 'all' || item.trend === filterType;
return matchesSearch && matchesFilter; return matchesSearch && matchesFilter;
}); });

View File

@@ -258,7 +258,7 @@ const Purchases: React.FC = () => {
<Loader2 className="animate-spin inline-block mr-2" /> Loading records... <Loader2 className="animate-spin inline-block mr-2" /> Loading records...
</td> </td>
</tr> </tr>
) : orders.filter(o => o.purchaseId.toLowerCase().includes(searchTerm.toLowerCase())).map((order) => ( ) : orders.filter(o => (o.purchaseId || '').toLowerCase().includes((searchTerm || '').toLowerCase())).map((order) => (
<tr key={order.id} className="hover:bg-gray-50/50 transition-all font-medium"> <tr key={order.id} className="hover:bg-gray-50/50 transition-all font-medium">
<td className="px-6 py-4 text-sm font-bold text-[#231651]">{order.purchaseId}</td> <td className="px-6 py-4 text-sm font-bold text-[#231651]">{order.purchaseId}</td>
<td className="px-6 py-4 text-sm text-[#64748b]">{new Date(order.date).toLocaleDateString()}</td> <td className="px-6 py-4 text-sm text-[#64748b]">{new Date(order.date).toLocaleDateString()}</td>

View File

@@ -109,10 +109,11 @@ const Staff = () => {
} }
}; };
const filteredStaff = staffList.filter(s => const filteredStaff = staff.filter(s => {
s.name.toLowerCase().includes(searchQuery.toLowerCase()) || const query = (searchQuery || '').toLowerCase();
s.email.toLowerCase().includes(searchQuery.toLowerCase()) return (s.name || '').toLowerCase().includes(query) ||
); (s.email || '').toLowerCase().includes(query);
});
return ( return (
<div className="p-8 space-y-8 bg-[#f8fafc] min-h-screen font-inter"> <div className="p-8 space-y-8 bg-[#f8fafc] min-h-screen font-inter">

View File

@@ -198,8 +198,15 @@ const Stalls: React.FC = () => {
fetch(`http://${host}:8080/api/base-items`) fetch(`http://${host}:8080/api/base-items`)
]); ]);
if (prodRes.ok) setAllProducts(await prodRes.json()); if (prodRes.ok) {
if (baseRes.ok) setAllBaseItems(await baseRes.json()); 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) { } catch (error) {
console.error('Error fetching available items:', error); console.error('Error fetching available items:', error);
} }
@@ -234,10 +241,11 @@ const Stalls: React.FC = () => {
} }
}; };
const filteredStalls = stalls.filter(s => const filteredStalls = stalls.filter(s => {
s.name.toLowerCase().includes(searchQuery.toLowerCase()) || const query = (searchQuery || '').toLowerCase();
s.description.toLowerCase().includes(searchQuery.toLowerCase()) return (s.name || '').toLowerCase().includes(query) ||
); (s.description || '').toLowerCase().includes(query);
});
const isStallOpen = (stall: Stall) => { const isStallOpen = (stall: Stall) => {
if (stall.temporarilyClosed) return false; if (stall.temporarilyClosed) return false;
@@ -618,7 +626,7 @@ const Stalls: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-4">
{activeTab === 'products' ? ( {activeTab === 'products' ? (
allProducts allProducts
.filter(p => p.name.toLowerCase().includes(itemSearchQuery.toLowerCase())) .filter(p => (p.name || '').toLowerCase().includes((itemSearchQuery || '').toLowerCase()))
.map(product => { .map(product => {
const isSelected = tempProductIds.includes(product.id); const isSelected = tempProductIds.includes(product.id);
return ( return (
@@ -644,7 +652,7 @@ const Stalls: React.FC = () => {
}) })
) : ( ) : (
allBaseItems allBaseItems
.filter(b => b.name.toLowerCase().includes(itemSearchQuery.toLowerCase())) .filter(b => (b.name || '').toLowerCase().includes((itemSearchQuery || '').toLowerCase()))
.map(baseItem => { .map(baseItem => {
const isSelected = tempBaseItemIds.includes(baseItem.id); const isSelected = tempBaseItemIds.includes(baseItem.id);
return ( return (

View File

@@ -60,11 +60,11 @@ const Terminals = () => {
} }
}; };
const filteredTerminals = terminals.filter(t => const filteredTerminals = terminals.filter(t => {
t.name.toLowerCase().includes(searchQuery.toLowerCase()) || const query = (searchQuery || '').toLowerCase();
t.location.toLowerCase().includes(searchQuery.toLowerCase()) return (t.name || '').toLowerCase().includes(query) ||
); (t.location || '').toLowerCase().includes(query);
});
return ( return (
<div className="p-8 max-w-7xl mx-auto space-y-8 font-inter"> <div className="p-8 max-w-7xl mx-auto space-y-8 font-inter">
{/* Header Section */} {/* Header Section */}

View File

@@ -134,10 +134,13 @@ const Vendors: React.FC = () => {
} }
}; };
const filteredVendors = vendors.filter(v => const filteredVendors = vendors.filter(v => {
v.name?.toLowerCase().includes(searchTerm.toLowerCase()) || if (!v) return false;
v.companyName?.toLowerCase().includes(searchTerm.toLowerCase()) const search = (searchTerm || '').toLowerCase();
); const name = (v.name || '').toLowerCase();
const company = (v.companyName || '').toLowerCase();
return name.includes(search) || company.includes(search);
});
return ( return (
<> <>