feat: adding clients crud
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								public/c1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/c1.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 80 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/c2.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/c2.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 58 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/c3.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/c3.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 152 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/c4.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/c4.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 93 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/c5.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/c5.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 39 KiB | 
							
								
								
									
										156
									
								
								src/private/clients/AddOrEditClientsForm.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								src/private/clients/AddOrEditClientsForm.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | |||||||
|  | import { useState, useEffect } from 'react'; | ||||||
|  | import { Box, Button, TextField, Grid, Avatar, Typography, Paper } from '@mui/material'; | ||||||
|  |  | ||||||
|  | export default function AddOrEditClientsForm({ onAdd, initialData, onCancel }) { | ||||||
|  |     const [client, setClient] = useState({ | ||||||
|  |         fullName: '', | ||||||
|  |         email: '', | ||||||
|  |         phone: '', | ||||||
|  |         address: '', | ||||||
|  |         company: '', | ||||||
|  |         avatar: '' | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     useEffect(() => { | ||||||
|  |         if (initialData) { | ||||||
|  |             setClient(initialData); | ||||||
|  |         } else { | ||||||
|  |             setClient({ | ||||||
|  |                 fullName: '', | ||||||
|  |                 email: '', | ||||||
|  |                 phone: '', | ||||||
|  |                 address: '', | ||||||
|  |                 company: '', | ||||||
|  |                 avatar: '' | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }, [initialData]); | ||||||
|  |  | ||||||
|  |     const handleChange = (e) => { | ||||||
|  |         const { name, value } = e.target; | ||||||
|  |         setClient((prev) => ({ ...prev, [name]: value })); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const handleImageChange = (e) => { | ||||||
|  |         const file = e.target.files[0]; | ||||||
|  |         if (!file) return; | ||||||
|  |  | ||||||
|  |         const reader = new FileReader(); | ||||||
|  |         reader.onloadend = () => { | ||||||
|  |             setClient((prev) => ({ ...prev, avatar: reader.result })); | ||||||
|  |         }; | ||||||
|  |         reader.readAsDataURL(file); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const handleSubmit = () => { | ||||||
|  |         if (onAdd) { | ||||||
|  |             onAdd(client); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return ( | ||||||
|  |         <Box sx={{ px: 2, py: 3 }}> | ||||||
|  |             <Box display="flex" flexDirection={{ xs: 'column', md: 'row' }} gap={4}> | ||||||
|  |                 {/* Left visual panel */} | ||||||
|  |                 <Paper | ||||||
|  |                     elevation={0} | ||||||
|  |                     sx={{ | ||||||
|  |                         bgcolor: '#f9f9f9', | ||||||
|  |                         p: 2, | ||||||
|  |                         borderRadius: 2, | ||||||
|  |                         flex: 1, | ||||||
|  |                         minWidth: '400px', | ||||||
|  |                     }} | ||||||
|  |                 > | ||||||
|  |                     <Typography variant="subtitle1" fontWeight={600} gutterBottom> | ||||||
|  |                         {client.fullName || 'N/A'} | ||||||
|  |                     </Typography> | ||||||
|  |                     <Typography variant="body2" gutterBottom> | ||||||
|  |                         {client.email || 'N/A'} | ||||||
|  |                     </Typography> | ||||||
|  |                     <Typography variant="body2" gutterBottom> | ||||||
|  |                         {client.phone || 'N/A'} | ||||||
|  |                     </Typography> | ||||||
|  |                     {client.avatar ? ( | ||||||
|  |                         <Avatar | ||||||
|  |                             variant="rounded" | ||||||
|  |                             src={client.avatar} | ||||||
|  |                             sx={{ width: '100%', height: 280, borderRadius: 2 }} | ||||||
|  |                             imgProps={{ style: { objectFit: 'cover', width: '100%', height: '100%' } }} | ||||||
|  |                         /> | ||||||
|  |                     ) : ( | ||||||
|  |                         <Box | ||||||
|  |                             sx={{ | ||||||
|  |                                 width: '100%', | ||||||
|  |                                 height: 240, | ||||||
|  |                                 bgcolor: '#e0e0e0', | ||||||
|  |                                 borderRadius: 2, | ||||||
|  |                                 display: 'flex', | ||||||
|  |                                 alignItems: 'center', | ||||||
|  |                                 justifyContent: 'center' | ||||||
|  |                             }} | ||||||
|  |                         > | ||||||
|  |                             <Typography variant="body2" color="text.secondary">Image Preview</Typography> | ||||||
|  |                         </Box> | ||||||
|  |                     )} | ||||||
|  |                     <Box mt={2}> | ||||||
|  |                         <Button component="label" className="button-transparent" fullWidth > | ||||||
|  |                             Upload Image | ||||||
|  |                             <input type="file" hidden accept="image/*" onChange={handleImageChange} /> | ||||||
|  |                         </Button> | ||||||
|  |                     </Box> | ||||||
|  |                 </Paper> | ||||||
|  |  | ||||||
|  |                 {/* Right input panel */} | ||||||
|  |                 <Box flex={1}> | ||||||
|  |                     <TextField | ||||||
|  |                         fullWidth | ||||||
|  |                         label="Full Name" | ||||||
|  |                         name="fullName" | ||||||
|  |                         value={client.fullName} | ||||||
|  |                         onChange={handleChange} | ||||||
|  |                         margin="normal" | ||||||
|  |                     /> | ||||||
|  |                     <TextField | ||||||
|  |                         fullWidth | ||||||
|  |                         label="Email" | ||||||
|  |                         name="email" | ||||||
|  |                         type="email" | ||||||
|  |                         value={client.email} | ||||||
|  |                         onChange={handleChange} | ||||||
|  |                         margin="normal" | ||||||
|  |                     /> | ||||||
|  |                     <TextField | ||||||
|  |                         fullWidth | ||||||
|  |                         label="Phone" | ||||||
|  |                         name="phone" | ||||||
|  |                         value={client.phone} | ||||||
|  |                         onChange={handleChange} | ||||||
|  |                         margin="normal" | ||||||
|  |                     /> | ||||||
|  |                     <TextField | ||||||
|  |                         fullWidth | ||||||
|  |                         label="Address" | ||||||
|  |                         name="address" | ||||||
|  |                         value={client.address} | ||||||
|  |                         onChange={handleChange} | ||||||
|  |                         margin="normal" | ||||||
|  |                     /> | ||||||
|  |                     <TextField | ||||||
|  |                         fullWidth | ||||||
|  |                         label="Company" | ||||||
|  |                         name="company" | ||||||
|  |                         value={client.company} | ||||||
|  |                         onChange={handleChange} | ||||||
|  |                         margin="normal" | ||||||
|  |                     /> | ||||||
|  |  | ||||||
|  |                     <Box display="flex" justifyContent="flex-end" gap={1} mt={3}> | ||||||
|  |                         <Button onClick={onCancel} className='button-transparent'>Cancel</Button> | ||||||
|  |                         <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> | ||||||
|  |                     </Box> | ||||||
|  |                 </Box> | ||||||
|  |             </Box> | ||||||
|  |         </Box> | ||||||
|  |     ); | ||||||
|  | } | ||||||
| @@ -2,11 +2,87 @@ import SectionContainer from '../../components/SectionContainer.jsx'; | |||||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||||
| import { DataGrid } from '@mui/x-data-grid'; | import { DataGrid } from '@mui/x-data-grid'; | ||||||
| import { Typography, Button, Dialog, DialogTitle, DialogContent, IconButton, Box, Avatar } from '@mui/material'; | import { Typography, Button, Dialog, DialogTitle, DialogContent, IconButton, Box, Avatar } from '@mui/material'; | ||||||
|  | import AddOrEditClientsForm from './AddOrEditClientsForm.jsx'; | ||||||
|  |  | ||||||
| import EditRoundedIcon from '@mui/icons-material/EditRounded'; | import EditRoundedIcon from '@mui/icons-material/EditRounded'; | ||||||
| import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | ||||||
| import '../../App.css'; | import '../../App.css'; | ||||||
|  |  | ||||||
| const columns = [ | export default function Clients() { | ||||||
|  |   const [rows, setRows] = useState([ | ||||||
|  |     { | ||||||
|  |       id: 1, | ||||||
|  |       avatar: '/c2.jpg', | ||||||
|  |       fullName: 'Anna Wintour', | ||||||
|  |       email: 'anna@fendi.com', | ||||||
|  |       phone: '+1 555-1234', | ||||||
|  |       address: '123 Fashion Blvd, NY', | ||||||
|  |       company: 'Fendi Casa' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       id: 2, | ||||||
|  |       avatar: '/c3.jpg', | ||||||
|  |       fullName: 'Karl Lagerfeld', | ||||||
|  |       email: 'karl@fendi.com', | ||||||
|  |       phone: '+1 555-5678', | ||||||
|  |       address: '456 Style Ave, Paris', | ||||||
|  |       company: 'Fendi Casa' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       id: 3, | ||||||
|  |       avatar: '/c4.jpg', | ||||||
|  |       fullName: 'Donatella Versace', | ||||||
|  |       email: 'donatella@fendi.com', | ||||||
|  |       phone: '+1 555-9999', | ||||||
|  |       address: '789 Couture St, Milan', | ||||||
|  |       company: 'Fendi Casa' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       id: 4, | ||||||
|  |       avatar: '/c5.jpg', | ||||||
|  |       fullName: 'Giorgio Armani', | ||||||
|  |       email: 'giorgio@fendi.com', | ||||||
|  |       phone: '+1 555-8888', | ||||||
|  |       address: '101 Luxury Rd, Milan', | ||||||
|  |       company: 'Fendi Casa' | ||||||
|  |     } | ||||||
|  |   ]); | ||||||
|  |  | ||||||
|  |   const [open, setOpen] = useState(false); | ||||||
|  |   const [editingClient, setEditingClient] = useState(null); | ||||||
|  |   const [confirmOpen, setConfirmOpen] = useState(false); | ||||||
|  |   const [rowToDelete, setRowToDelete] = useState(null); | ||||||
|  |  | ||||||
|  |   const handleAddOrEditClient = (client) => { | ||||||
|  |     if (editingClient) { | ||||||
|  |       // Update existing | ||||||
|  |       setRows(rows.map((row) => (row.id === editingClient.id ? { ...editingClient, ...client } : row))); | ||||||
|  |     } else { | ||||||
|  |       // Add new | ||||||
|  |       const id = rows.length + 1; | ||||||
|  |       setRows([...rows, { id, company: 'Fendi casa', ...client }]); | ||||||
|  |     } | ||||||
|  |     setOpen(false); | ||||||
|  |     setEditingClient(null); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleEditClick = (params) => { | ||||||
|  |     setEditingClient(params.row); | ||||||
|  |     setOpen(true); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleDeleteClick = (row) => { | ||||||
|  |     setRowToDelete(row); | ||||||
|  |     setConfirmOpen(true); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const confirmDelete = () => { | ||||||
|  |     setRows(rows.filter((row) => row.id !== rowToDelete.id)); | ||||||
|  |     setRowToDelete(null); | ||||||
|  |     setConfirmOpen(false); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const columns = [ | ||||||
|     { |     { | ||||||
|       field: 'avatar', |       field: 'avatar', | ||||||
|       headerName: '', |       headerName: '', | ||||||
| @@ -42,6 +118,7 @@ const columns = [ | |||||||
|               borderRadius: 2, |               borderRadius: 2, | ||||||
|               p: 1, |               p: 1, | ||||||
|             }} |             }} | ||||||
|  |             onClick={() => handleEditClick(params)} | ||||||
|           > |           > | ||||||
|             <EditRoundedIcon fontSize="small" /> |             <EditRoundedIcon fontSize="small" /> | ||||||
|           </IconButton> |           </IconButton> | ||||||
| @@ -54,50 +131,42 @@ const columns = [ | |||||||
|               borderRadius: 2, |               borderRadius: 2, | ||||||
|               p: 1, |               p: 1, | ||||||
|             }} |             }} | ||||||
|  |             onClick={() => handleDeleteClick(params.row)} | ||||||
|           > |           > | ||||||
|             <DeleteRoundedIcon fontSize="small" /> |             <DeleteRoundedIcon fontSize="small" /> | ||||||
|           </IconButton> |           </IconButton> | ||||||
|         </Box> |         </Box> | ||||||
|       ) |       ) | ||||||
|     } |     } | ||||||
| ]; |   ]; | ||||||
|  |  | ||||||
| export default function Clients() { |  | ||||||
|   const [rows, setRows] = useState([ |  | ||||||
|     { |  | ||||||
|       id: 1, |  | ||||||
|       avatar: '/client1.jpg', |  | ||||||
|       fullName: 'Anna Wintour', |  | ||||||
|       email: 'anna@fendi.com', |  | ||||||
|       phone: '+1 555-1234', |  | ||||||
|       address: '123 Fashion Blvd, NY', |  | ||||||
|       company: 'Fendi Casa' |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       id: 2, |  | ||||||
|       avatar: '/client2.jpg', |  | ||||||
|       fullName: 'Karl Lagerfeld', |  | ||||||
|       email: 'karl@fendi.com', |  | ||||||
|       phone: '+1 555-5678', |  | ||||||
|       address: '456 Style Ave, Paris', |  | ||||||
|       company: 'Fendi Casa' |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       id: 3, |  | ||||||
|       avatar: '', |  | ||||||
|       fullName: 'Donatella Versace', |  | ||||||
|       email: 'donatella@fendi.com', |  | ||||||
|       phone: '+1 555-9999', |  | ||||||
|       address: '789 Couture St, Milan', |  | ||||||
|       company: 'Fendi Casa' |  | ||||||
|     } |  | ||||||
|   ]); |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <SectionContainer sx={{ width: '100%' }}> |     <SectionContainer sx={{ width: '100%' }}> | ||||||
|       <Typography variant="h4" gutterBottom color="#26201AFF"> |       <Typography variant="h4" gutterBottom color="#26201AFF"> | ||||||
|         Clients |         Clients | ||||||
|       </Typography> |       </Typography> | ||||||
|  |  | ||||||
|  |       <Dialog open={open} onClose={() => { setOpen(false); setEditingClient(null); }} maxWidth="md" fullWidth> | ||||||
|  |         <DialogTitle>{editingClient ? 'Edit Client' : 'Add Client'}</DialogTitle> | ||||||
|  |         <DialogContent> | ||||||
|  |           <AddOrEditClientsForm onAdd={handleAddOrEditClient} initialData={editingClient} onCancel={() => { setOpen(false); setEditingClient(null); }} /> | ||||||
|  |         </DialogContent> | ||||||
|  |       </Dialog> | ||||||
|  |  | ||||||
|  |       <Dialog open={confirmOpen} onClose={() => setConfirmOpen(false)}> | ||||||
|  |         <DialogTitle>Confirm Delete</DialogTitle> | ||||||
|  |         <DialogContent> | ||||||
|  |           <Typography> | ||||||
|  |             Are you sure you want to delete{' '} | ||||||
|  |             <strong>{rowToDelete?.fullName}</strong>? | ||||||
|  |           </Typography> | ||||||
|  |           <Box mt={2} display="flex" justifyContent="flex-end" gap={1}> | ||||||
|  |             <Button onClick={() => setConfirmOpen(false)} className='button-transparent'>Cancel</Button> | ||||||
|  |             <Button variant="contained" onClick={confirmDelete} className="button-gold">Delete</Button> | ||||||
|  |           </Box> | ||||||
|  |         </DialogContent> | ||||||
|  |       </Dialog> | ||||||
|  |  | ||||||
|       <Box mt={2}> |       <Box mt={2}> | ||||||
|         <DataGrid |         <DataGrid | ||||||
|           getRowHeight={() => 60} |           getRowHeight={() => 60} | ||||||
| @@ -108,7 +177,7 @@ export default function Clients() { | |||||||
|           getRowSpacing={() => ({ top: 4, bottom: 4 })} |           getRowSpacing={() => ({ top: 4, bottom: 4 })} | ||||||
|         /> |         /> | ||||||
|         <Box display="flex" justifyContent="flex-end" mt={2}> |         <Box display="flex" justifyContent="flex-end" mt={2}> | ||||||
|           <Button variant="contained" className="button-gold"> |           <Button variant="contained" className="button-gold" onClick={() => setOpen(true)}> | ||||||
|             Add Client |             Add Client | ||||||
|           </Button> |           </Button> | ||||||
|         </Box> |         </Box> | ||||||
|   | |||||||
| @@ -131,7 +131,7 @@ export default function Products({ children, maxWidth = 'lg', sx = {} }) { | |||||||
|     return ( |     return ( | ||||||
|         <SectionContainer sx={{ width: '100%' }}> |         <SectionContainer sx={{ width: '100%' }}> | ||||||
|             <Typography variant="h4" gutterBottom color='#26201AFF'> |             <Typography variant="h4" gutterBottom color='#26201AFF'> | ||||||
|                 Product Catalog |                 Products | ||||||
|             </Typography> |             </Typography> | ||||||
|  |  | ||||||
|             <Dialog open={open} onClose={() => { setOpen(false); setEditingProduct(null); }} maxWidth="md" fullWidth> |             <Dialog open={open} onClose={() => { setOpen(false); setEditingProduct(null); }} maxWidth="md" fullWidth> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Rodolfo Ruiz
					Rodolfo Ruiz