feat: new screens and docker setup
This commit is contained in:
		| @@ -11,9 +11,11 @@ | ||||
|   will-change: filter; | ||||
|   transition: filter 300ms; | ||||
| } | ||||
|  | ||||
| .logo:hover { | ||||
|   filter: drop-shadow(0 0 2em #646cffaa); | ||||
| } | ||||
|  | ||||
| .logo.react:hover { | ||||
|   filter: drop-shadow(0 0 2em #61dafbaa); | ||||
| } | ||||
| @@ -22,6 +24,7 @@ | ||||
|   from { | ||||
|     transform: rotate(0deg); | ||||
|   } | ||||
|  | ||||
|   to { | ||||
|     transform: rotate(360deg); | ||||
|   } | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import Box from '@mui/material/Box'; | ||||
| import Products from './private/products/Products'; | ||||
| import Clients from './private/clients/Clients'; | ||||
| import Providers from './private/providers/Providers'; | ||||
| import Categories from './private/categories/Categories'; | ||||
|  | ||||
| function App() { | ||||
|   const [zone, setZone] = useState('public'); // Could be 'public' | 'restricted' | 'private' | ||||
| @@ -31,7 +32,8 @@ function App() { | ||||
|  | ||||
|           {zone === 'public' && currentView === 'Products' && <Products />} | ||||
|           {zone === 'public' && currentView === 'Clients' && <Clients />} | ||||
|            {zone === 'public' && currentView === 'Providers' && <Providers />} | ||||
|           {zone === 'public' && currentView === 'Providers' && <Providers />} | ||||
|           {zone === 'public' && currentView === 'Categories' && <Categories />} | ||||
|         </Box> | ||||
|         <Footer zone={zone} /> | ||||
|       </Box> | ||||
|   | ||||
| @@ -1,9 +1,27 @@ | ||||
| import { Drawer, List, ListItem, ListItemText, useMediaQuery } from '@mui/material'; | ||||
| import { Drawer, List, ListItem, ListItemText, ListItemIcon, Avatar, Typography, Box, useMediaQuery } from '@mui/material'; | ||||
| import CategoryIcon from '@mui/icons-material/Category'; | ||||
| import PeopleIcon from '@mui/icons-material/People'; | ||||
| import InventoryIcon from '@mui/icons-material/Inventory'; | ||||
| import LocalShippingIcon from '@mui/icons-material/LocalShipping'; | ||||
| import ExitToAppIcon from '@mui/icons-material/ExitToApp'; | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| const menuOptions = { | ||||
|   public: ['Home', 'Explore', 'Contact'], | ||||
|   restricted: ['Dashboard', 'Projects', 'Support'], | ||||
|   private: ['Products', 'Clients', 'Providers', 'Logout'], | ||||
|   public: [ | ||||
|     { text: 'Categories', icon: <CategoryIcon /> }, | ||||
|     { text: 'Clients', icon: <PeopleIcon /> }, | ||||
|     { text: 'Products', icon: <InventoryIcon /> }, | ||||
|     { text: 'Providers', icon: <LocalShippingIcon /> }, | ||||
|     { text: 'Logout', icon: <ExitToAppIcon /> }, | ||||
|   ], | ||||
|   restricted: [], | ||||
|   private: [ | ||||
|     { text: 'Categories', icon: <CategoryIcon /> }, | ||||
|     { text: 'Clients', icon: <PeopleIcon /> }, | ||||
|     { text: 'Products', icon: <InventoryIcon /> }, | ||||
|     { text: 'Providers', icon: <LocalShippingIcon /> }, | ||||
|     { text: 'Logout', icon: <ExitToAppIcon /> }, | ||||
|   ], | ||||
| }; | ||||
|  | ||||
| export default function MenuDrawer({ zone = 'public', open, onClose, onSelect }) { | ||||
| @@ -16,15 +34,27 @@ export default function MenuDrawer({ zone = 'public', open, onClose, onSelect }) | ||||
|         sx: { | ||||
|           backgroundColor: '#40120EFF', | ||||
|           width: isMobile ? '100vw' : 250, | ||||
|           color: '#DFCCBCFF' | ||||
|         }, | ||||
|       }, | ||||
|     }}> | ||||
|       <List sx={{ width: isMobile ? '100vw' : 250, marginTop: 14 }}> | ||||
|         {items.map((text, index) => ( | ||||
|       <Box textAlign="center" p={3}> | ||||
|         <Avatar | ||||
|           src="/favicon.png" | ||||
|           alt="User" | ||||
|           sx={{ width: 64, height: 64, mx: 'auto', mb: 1 }} | ||||
|         /> | ||||
|         <Typography variant="subtitle1" fontWeight={600}>Fendi Casa</Typography> | ||||
|         <Typography variant="body2">Administrator</Typography> | ||||
|       </Box> | ||||
|       <List sx={{ width: isMobile ? '100vw' : 250, marginTop: 2 }}> | ||||
|         {items.map(({ text, icon }, index) => ( | ||||
|           <ListItem key={index} onClick={() => { | ||||
|             onClose(); // Close drawer | ||||
|             onSelect?.(text); // Notify parent of selected item | ||||
|           }}> | ||||
|  | ||||
|             <ListItemIcon sx={{ color: '#DFCCBCFF' }}>{icon}</ListItemIcon> | ||||
|             <ListItemText | ||||
|               primary={text} | ||||
|               slotProps={{ | ||||
| @@ -41,4 +71,4 @@ export default function MenuDrawer({ zone = 'public', open, onClose, onSelect }) | ||||
|       </List> | ||||
|     </Drawer> | ||||
|   ); | ||||
| } | ||||
| } | ||||
|   | ||||
							
								
								
									
										64
									
								
								src/private/categories/AddOrEditCategoryForm.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/private/categories/AddOrEditCategoryForm.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| import { useState, useEffect } from 'react'; | ||||
| import { Box, Button, TextField, Typography, Paper } from '@mui/material'; | ||||
|  | ||||
| export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) { | ||||
|     const [category, setCategory] = useState({ | ||||
|         name: '', | ||||
|         description: '' | ||||
|     }); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         if (initialData) { | ||||
|             setCategory(initialData); | ||||
|         } else { | ||||
|             setCategory({ name: '', description: '' }); | ||||
|         } | ||||
|     }, [initialData]); | ||||
|  | ||||
|     const handleChange = (e) => { | ||||
|         const { name, value } = e.target; | ||||
|         setCategory((prev) => ({ ...prev, [name]: value })); | ||||
|     }; | ||||
|  | ||||
|     const handleSubmit = () => { | ||||
|         if (onAdd) { | ||||
|             onAdd(category); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     return ( | ||||
|         <Box sx={{ px: 2, py: 3 }}> | ||||
|             <Paper elevation={0} sx={{ p: 3, bgcolor: '#f9f9f9', borderRadius: 2 }}> | ||||
|                 <Typography variant="h6" gutterBottom> | ||||
|                     Category Details | ||||
|                 </Typography> | ||||
|                 <TextField | ||||
|                     fullWidth | ||||
|                     label="Name" | ||||
|                     name="name" | ||||
|                     value={category.name} | ||||
|                     onChange={handleChange} | ||||
|                     margin="normal" | ||||
|                 /> | ||||
|                 <TextField | ||||
|                     fullWidth | ||||
|                     label="Description" | ||||
|                     name="description" | ||||
|                     value={category.description} | ||||
|                     onChange={handleChange} | ||||
|                     margin="normal" | ||||
|                     multiline | ||||
|                     rows={4} | ||||
|                 /> | ||||
|                 <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> | ||||
|             </Paper> | ||||
|         </Box> | ||||
|     ); | ||||
| } | ||||
							
								
								
									
										142
									
								
								src/private/categories/Categories.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/private/categories/Categories.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | ||||
| import SectionContainer from '../../components/SectionContainer.jsx'; | ||||
| import { useState } from 'react'; | ||||
| import { DataGrid } from '@mui/x-data-grid'; | ||||
| import { Typography, Button, Dialog, DialogTitle, DialogContent, IconButton, Box } from '@mui/material'; | ||||
| import AddOrEditCategoryForm from './AddOrEditCategoryForm.jsx'; | ||||
|  | ||||
| import EditRoundedIcon from '@mui/icons-material/EditRounded'; | ||||
| import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | ||||
| import '../../App.css'; | ||||
|  | ||||
| const columnsBase = [ | ||||
|     { field: 'name', headerName: 'Name', flex: 1 }, | ||||
|     { field: 'description', headerName: 'Description', flex: 2 } | ||||
| ]; | ||||
|  | ||||
| export default function Categories({ children, maxWidth = 'lg', sx = {} }) { | ||||
|     const [rows, setRows] = useState([ | ||||
|         { id: 1, name: 'Fabrics', description: 'Textile materials including silk, cotton, and synthetics.' }, | ||||
|         { id: 2, name: 'Leather Goods', description: 'Leather-based components for luxury goods.' }, | ||||
|         { id: 3, name: 'Metal Accessories', description: 'Buttons, zippers, and hardware in metal.' }, | ||||
|         { id: 4, name: 'Embellishments', description: 'Decorative materials such as beads and sequins.' } | ||||
|     ]); | ||||
|  | ||||
|     const [open, setOpen] = useState(false); | ||||
|     const [editingCategory, setEditingCategory] = useState(null); | ||||
|     const [confirmOpen, setConfirmOpen] = useState(false); | ||||
|     const [rowToDelete, setRowToDelete] = useState(null); | ||||
|  | ||||
|     const handleAddOrEditCategory = (category) => { | ||||
|         if (editingCategory) { | ||||
|             setRows(rows.map((row) => (row.id === editingCategory.id ? { ...editingCategory, ...category } : row))); | ||||
|         } else { | ||||
|             const id = rows.length + 1; | ||||
|             setRows([...rows, { id, ...category }]); | ||||
|         } | ||||
|         setOpen(false); | ||||
|         setEditingCategory(null); | ||||
|     }; | ||||
|  | ||||
|     const handleEditClick = (params) => { | ||||
|         setEditingCategory(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 = [ | ||||
|         ...columnsBase, | ||||
|         { | ||||
|             field: 'actions', | ||||
|             headerName: '', | ||||
|             width: 130, | ||||
|             renderCell: (params) => ( | ||||
|                 <Box display="flex" alignItems="center" justifyContent="flex-end" height="100%" gap={2}> | ||||
|                     <IconButton | ||||
|                         size="small" | ||||
|                         sx={{ | ||||
|                             backgroundColor: '#DFCCBC', | ||||
|                             color: '#26201A', | ||||
|                             '&:hover': { | ||||
|                                 backgroundColor: '#C2B2A4', | ||||
|                             }, | ||||
|                             borderRadius: 2, | ||||
|                             p: 1, | ||||
|                         }} | ||||
|                         onClick={() => handleEditClick(params)} | ||||
|                     > | ||||
|                         <EditRoundedIcon fontSize="small" /> | ||||
|                     </IconButton> | ||||
|                     <IconButton | ||||
|                         size="small" | ||||
|                         sx={{ | ||||
|                             backgroundColor: '#FBE9E7', | ||||
|                             color: '#C62828', | ||||
|                             '&:hover': { | ||||
|                                 backgroundColor: '#EF9A9A', | ||||
|                             }, | ||||
|                             borderRadius: 2, | ||||
|                             p: 1, | ||||
|                         }} | ||||
|                         onClick={() => handleDeleteClick(params.row)} | ||||
|                     > | ||||
|                         <DeleteRoundedIcon fontSize="small" /> | ||||
|                     </IconButton> | ||||
|                 </Box> | ||||
|             ) | ||||
|         } | ||||
|     ]; | ||||
|  | ||||
|     return ( | ||||
|         <SectionContainer sx={{ width: '100%' }}> | ||||
|             <Typography variant="h4" gutterBottom color='#26201AFF'> | ||||
|                 Categories | ||||
|             </Typography> | ||||
|  | ||||
|             <Dialog open={open} onClose={() => { setOpen(false); setEditingCategory(null); }} fullWidth> | ||||
|                 <DialogTitle>{editingCategory ? 'Edit Category' : 'Add Category'}</DialogTitle> | ||||
|                 <DialogContent> | ||||
|                     <AddOrEditCategoryForm onAdd={handleAddOrEditCategory} initialData={editingCategory} onCancel={() => { setOpen(false); setEditingCategory(null); }} /> | ||||
|                 </DialogContent> | ||||
|             </Dialog> | ||||
|  | ||||
|             <Dialog open={confirmOpen} onClose={() => setConfirmOpen(false)}> | ||||
|                 <DialogTitle>Confirm Delete</DialogTitle> | ||||
|                 <DialogContent> | ||||
|                     <Typography> | ||||
|                         Are you sure you want to delete <strong>{rowToDelete?.name}</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}> | ||||
|                 <DataGrid | ||||
|                     rows={rows} | ||||
|                     columns={columns} | ||||
|                     pageSize={5} | ||||
|                     rowsPerPageOptions={[5]} | ||||
|                     getRowSpacing={() => ({ top: 8, bottom: 8 })} | ||||
|                 /> | ||||
|  | ||||
|                 <Box display="flex" justifyContent="flex-end" mt={2}> | ||||
|                     <Button variant="contained" onClick={() => setOpen(true)} className="button-gold"> | ||||
|                         Add Category | ||||
|                     </Button> | ||||
|                 </Box> | ||||
|             </Box> | ||||
|         </SectionContainer> | ||||
|     ); | ||||
| } | ||||
| @@ -66,7 +66,7 @@ const theme = createTheme({ | ||||
|         root: { | ||||
|           transition: 'background-color 0.2s ease-in-out', | ||||
|           '&:hover': { | ||||
|             backgroundColor: '#AA7665FF',          | ||||
|             backgroundColor: '#AA7665FF', | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Rodolfo Ruiz
					Rodolfo Ruiz