Integrated stock reduction logic and branding updates
This commit is contained in:
@@ -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 = () => {
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : items.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())).length === 0 ? (
|
||||
) : items.filter(item => (item.name || '').toLowerCase().includes((searchTerm || '').toLowerCase())).length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={6} className="px-6 py-12 text-center text-[#64748b]">
|
||||
<p className="text-lg font-medium mb-1">No items found</p>
|
||||
@@ -209,7 +212,7 @@ const BaseMenu = () => {
|
||||
</tr>
|
||||
) : (
|
||||
items
|
||||
.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||
.filter(item => (item.name || '').toLowerCase().includes((searchTerm || '').toLowerCase()))
|
||||
.map((item) => (
|
||||
<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>
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -193,7 +193,11 @@ const Managers = () => {
|
||||
</tr>
|
||||
</thead>
|
||||
<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">
|
||||
<td className="px-6 py-5">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = () => {
|
||||
<tbody className="divide-y divide-[#e2e8f0]">
|
||||
{loading ? (
|
||||
<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>
|
||||
) : 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">
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -327,9 +359,9 @@ const Products = () => {
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{(() => {
|
||||
// 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
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -258,7 +258,7 @@ const Purchases: React.FC = () => {
|
||||
<Loader2 className="animate-spin inline-block mr-2" /> Loading records...
|
||||
</td>
|
||||
</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">
|
||||
<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>
|
||||
|
||||
@@ -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 (
|
||||
<div className="p-8 space-y-8 bg-[#f8fafc] min-h-screen font-inter">
|
||||
|
||||
@@ -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 = () => {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-4">
|
||||
{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 (
|
||||
|
||||
@@ -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 (
|
||||
<div className="p-8 max-w-7xl mx-auto space-y-8 font-inter">
|
||||
{/* Header Section */}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user