comment chagnes

This commit is contained in:
2ManyProjects 2025-05-05 15:37:48 -05:00
parent 2f32cb7deb
commit 96334a595f
4 changed files with 4182 additions and 173 deletions

3982
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -23,12 +23,14 @@
"axios": "^1.6.2",
"date-fns": "^4.1.0",
"dotenv": "^16.5.0",
"papaparse": "^5.5.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-email-editor": "^1.7.11",
"react-redux": "^9.0.2",
"react-router-dom": "^6.20.1",
"recharts": "^2.10.3"
"recharts": "^2.10.3",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/react": "^18.2.37",

View file

@ -0,0 +1,106 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import apiClient from '@services/api';
import { useNotification } from './reduxHooks';
import { useNavigate, useParams, useLocation } from 'react-router-dom';
// Fetch categories
export const useCategories = () => {
return useQuery({
queryKey: ['categories'],
queryFn: async () => {
const response = await apiClient.get('/products/categories/all');
return response.data;
}
})
};
// Fetch all available tags
export const useTags = () => {
return useQuery({
queryKey: ['tags'],
queryFn: async () => {
const response = await apiClient.get('/products/tags/all');
return response.data;
}
})
};
// Fetch product data if editing
export const useProduct = (id, isNewProduct) => {
return useQuery({
queryKey: ['product', id],
queryFn: async () => {
const response = await apiClient.get(`/products/${id}`);
return response.data[0];
},
enabled: !isNewProduct
})
};
// Create product mutation
export const useCreateProduct = () => {
const notification = useNotification();
const queryClient = useQueryClient();
const navigate = useNavigate();
return useMutation({
mutationFn: async (productData) => {
return await apiClient.post('/admin/products', productData);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin-products'] });
notification.showNotification('Product created successfully', 'success');
// Redirect after a short delay
setTimeout(() => {
navigate('/admin/products');
}, 1500);
},
onError: (error) => {
notification.showNotification(
`Failed to create product: ${error.message}`,
'error'
);
}
})
};
// Update product mutation
export const useUpdateProduct = (id) => {
const notification = useNotification();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, productData }) => {
return await apiClient.put(`/admin/products/${id}`, productData);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin-products'] });
queryClient.invalidateQueries({ queryKey: ['product', id] });
notification.showNotification('Product updated successfully', 'success');
},
onError: (error) => {
notification.showNotification(
`Failed to update product: ${error.message}`,
'error'
);
}
})
};
// Save stock notification settings
export const useSaveStockNotification = (id) => {
const notification = useNotification();
return useMutation({
mutationFn: async (notificationData) => {
return await apiClient.post(`/admin/products/${id}/stock-notification`, notificationData);
},
onSuccess: () => {
notification.showNotification('Stock notification settings saved!', 'success');
},
onError: (error) => {
notification.showNotification(
`Failed to save notification settings: ${error.message}`,
'error'
);
}
})
};

View file

@ -31,6 +31,7 @@ import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive';
import ImageUploader from '@components/ImageUploader';
import apiClient from '@services/api';
import { useAuth } from '@hooks/reduxHooks';
import { useCategories, useTags, useProduct, useCreateProduct, useUpdateProduct, useSaveStockNotification } from '@hooks/productAdminHooks';
const ProductEditPage = () => {
const { pathname } = useLocation();
@ -75,106 +76,26 @@ const ProductEditPage = () => {
});
// Fetch categories
const { data: categories, isLoading: categoriesLoading } = useQuery({
queryKey: ['categories'],
queryFn: async () => {
const response = await apiClient.get('/products/categories/all');
return response.data;
}
});
const { data: categories, isLoading: categoriesLoading } = useCategories();
// Fetch all available tags
const { data: allTags, isLoading: tagsLoading } = useQuery({
queryKey: ['tags'],
queryFn: async () => {
const response = await apiClient.get('/products/tags/all');
return response.data;
}
});
const { data: allTags, isLoading: tagsLoading } = useTags();
// Fetch product data if editing
const {
data: product,
isLoading: productLoading,
error: productError
} = useQuery({
queryKey: ['product', id],
queryFn: async () => {
const response = await apiClient.get(`/products/${id}`);
return response.data[0];
},
enabled: !isNewProduct
});
} = useProduct(id === 'new' ? null : id, isNewProduct);
// Create product mutation
const createProduct = useMutation({
mutationFn: async (productData) => {
return await apiClient.post('/admin/products', productData);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin-products'] });
setNotification({
open: true,
message: 'Product created successfully!',
severity: 'success'
});
// Redirect after a short delay
setTimeout(() => {
navigate('/admin/products');
}, 1500);
},
onError: (error) => {
setNotification({
open: true,
message: `Failed to create product: ${error.message}`,
severity: 'error'
});
}
});
const createProduct = useCreateProduct();
// Update product mutation
const updateProduct = useMutation({
mutationFn: async ({ id, productData }) => {
return await apiClient.put(`/admin/products/${id}`, productData);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin-products'] });
queryClient.invalidateQueries({ queryKey: ['product', id] });
setNotification({
open: true,
message: 'Product updated successfully!',
severity: 'success'
});
},
onError: (error) => {
setNotification({
open: true,
message: `Failed to update product: ${error.message}`,
severity: 'error'
});
}
});
const updateProduct = useUpdateProduct(id === 'new' ? null : id);
// Save stock notification settings
const saveStockNotification = useMutation({
mutationFn: async (notificationData) => {
return await apiClient.post(`/admin/products/${id}/stock-notification`, notificationData);
},
onSuccess: () => {
setNotification({
open: true,
message: 'Stock notification settings saved!',
severity: 'success'
});
},
onError: (error) => {
setNotification({
open: true,
message: `Failed to save notification settings: ${error.message}`,
severity: 'error'
});
}
});
const saveStockNotification = useSaveStockNotification(id === 'new' ? null : id);
// Handle form changes
const handleChange = (e) => {
@ -323,35 +244,34 @@ const ProductEditPage = () => {
};
// Add notification data if enabled
if (notificationEnabled && !isNewProduct) {
if (notificationEnabled) {
productData.stockNotification = {
enabled: true,
email: notificationEmail,
threshold: stockThreshold
};
}
if (isNewProduct) {
createProduct.mutate(productData);
} else {
updateProduct.mutate({ id, productData });
// Save notification settings separately
if (notificationEnabled) {
saveStockNotification.mutate({
enabled: true,
email: notificationEmail,
threshold: stockThreshold
});
} else {
// Disable notifications if checkbox is unchecked
saveStockNotification.mutate({
enabled: false,
email: '',
threshold: 0
});
}
}
// Save notification settings separately
// if (notificationEnabled) {
// saveStockNotification.mutate({
// enabled: true,
// email: notificationEmail,
// threshold: stockThreshold
// });
// } else {
// // Disable notifications if checkbox is unchecked
// saveStockNotification.mutate({
// enabled: false,
// email: '',
// threshold: 0
// });
// }
};
// Handle notification close
@ -545,78 +465,77 @@ const ProductEditPage = () => {
</Grid>
{/* Stock Notification Section */}
{!isNewProduct && (
<>
<Grid item xs={12}>
<Divider sx={{ my: 2 }} />
<Typography variant="h6" gutterBottom>
Stock Level Notifications
</Typography>
</Grid>
<Grid item xs={12}>
<Card variant="outlined" sx={{ bgcolor: 'background.paper' }}>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<NotificationsActiveIcon color="primary" sx={{ mr: 1 }} />
<Typography variant="subtitle1">
Get notified when stock is running low
</Typography>
</Box>
<>
<Grid item xs={12}>
<Divider sx={{ my: 2 }} />
<Typography variant="h6" gutterBottom>
Stock Level Notifications
</Typography>
</Grid>
<FormControlLabel
control={
<Checkbox
checked={notificationEnabled}
onChange={handleNotificationToggle}
name="notificationEnabled"
color="primary"
<Grid item xs={12}>
<Card variant="outlined" sx={{ bgcolor: 'background.paper' }}>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<NotificationsActiveIcon color="primary" sx={{ mr: 1 }} />
<Typography variant="subtitle1">
Get notified when stock is running low
</Typography>
</Box>
<FormControlLabel
control={
<Checkbox
checked={notificationEnabled}
onChange={handleNotificationToggle}
name="notificationEnabled"
color="primary"
/>
}
label="Enable stock level notifications"
/>
{notificationEnabled && (
<Grid container spacing={2} sx={{ mt: 1 }}>
<Grid item xs={12} md={6}>
<TextField
fullWidth
label="Notification Email"
name="notificationEmail"
type="email"
value={notificationEmail}
onChange={handleNotificationEmailChange}
error={!!errors.notificationEmail}
helperText={errors.notificationEmail}
required
/>
}
label="Enable stock level notifications"
/>
{notificationEnabled && (
<Grid container spacing={2} sx={{ mt: 1 }}>
<Grid item xs={12} md={6}>
<TextField
fullWidth
label="Notification Email"
name="notificationEmail"
type="email"
value={notificationEmail}
onChange={handleNotificationEmailChange}
error={!!errors.notificationEmail}
helperText={errors.notificationEmail}
required
/>
</Grid>
<Grid item xs={12} md={6}>
<TextField
fullWidth
label="Stock Threshold"
name="stockThreshold"
type="number"
value={stockThreshold}
onChange={handleStockThresholdChange}
error={!!errors.stockThreshold}
helperText={errors.stockThreshold || "You'll be notified when stock falls below this number"}
required
InputProps={{
inputProps: {
min: 1,
max: formData.stockQuantity ? parseInt(formData.stockQuantity) : 999
}
}}
/>
</Grid>
</Grid>
)}
</CardContent>
</Card>
</Grid>
</>
)}
<Grid item xs={12} md={6}>
<TextField
fullWidth
label="Stock Threshold"
name="stockThreshold"
type="number"
value={stockThreshold}
onChange={handleStockThresholdChange}
error={!!errors.stockThreshold}
helperText={errors.stockThreshold || "You'll be notified when stock falls below this number"}
required
InputProps={{
inputProps: {
min: 1,
max: formData.stockQuantity ? parseInt(formData.stockQuantity) : 999
}
}}
/>
</Grid>
</Grid>
)}
</CardContent>
</Card>
</Grid>
</>
<Grid item xs={12}>
<Divider sx={{ my: 2 }} />