feat: improve usability and ui minor changes
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								public/1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/1.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 363 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/2.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/2.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 737 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/3.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/3.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 203 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/4.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/4.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 772 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/5.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/5.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 164 KiB | 
| @@ -15,7 +15,7 @@ function App() { | |||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|  |  | ||||||
|       <Background imageName='background.jpg' opacity={0.65} /> |       {/* <Background imageName='background.jpg' opacity={0.65} /> */} | ||||||
|       {/* <VideoBackground videoId="1066622045" /> */} |       {/* <VideoBackground videoId="1066622045" /> */} | ||||||
|  |  | ||||||
|       <Box |       <Box | ||||||
|   | |||||||
| @@ -8,8 +8,8 @@ export default function AppHeader({ zone = 'public' }) { | |||||||
|  |  | ||||||
|   const bgColor = { |   const bgColor = { | ||||||
|     public: '#40120EFF', |     public: '#40120EFF', | ||||||
|     restricted: '#e0e0ff', |     restricted: '#40120EFF', | ||||||
|     private: '#d0f0e0', |     private: '#40120EFF', | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const [menuOpen, setMenuOpen] = useState(false); |   const [menuOpen, setMenuOpen] = useState(false); | ||||||
| @@ -22,7 +22,7 @@ export default function AppHeader({ zone = 'public' }) { | |||||||
|     <AppBar position="static" |     <AppBar position="static" | ||||||
|       sx={{ |       sx={{ | ||||||
|         textAlign: 'center', |         textAlign: 'center', | ||||||
|         bgcolor: bgColor[zone], |         backgroundColor: bgColor[zone], | ||||||
|         mt: 'auto', |         mt: 'auto', | ||||||
|         fontSize: { xs: '0.75rem', md: '1rem' }, |         fontSize: { xs: '0.75rem', md: '1rem' }, | ||||||
|       }} > |       }} > | ||||||
| @@ -44,7 +44,6 @@ export default function AppHeader({ zone = 'public' }) { | |||||||
|                 pr: 2, |                 pr: 2, | ||||||
|                 py: 0.5, |                 py: 0.5, | ||||||
|                 borderRadius: 1, |                 borderRadius: 1, | ||||||
|                 bgcolor: '#000000a0', |  | ||||||
|                 color: '#A68A72FF', |                 color: '#A68A72FF', | ||||||
|                 width: { md: '300px', lg: '400px' } |                 width: { md: '300px', lg: '400px' } | ||||||
|               }} |               }} | ||||||
|   | |||||||
| @@ -3,9 +3,9 @@ import fendiLogo from '/logo.png' | |||||||
|  |  | ||||||
| export default function Footer({ zone = 'public' }) { | export default function Footer({ zone = 'public' }) { | ||||||
|     const bgColor = { |     const bgColor = { | ||||||
|         public: '#000000a0', |         public: '#40120EFF', | ||||||
|         restricted: '#e0e0ff', |         restricted: '#40120EFF', | ||||||
|         private: '#d0f0e0', |         private: '#40120EFF', | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const year = new Date().getFullYear(); |     const year = new Date().getFullYear(); | ||||||
|   | |||||||
| @@ -28,13 +28,9 @@ body { | |||||||
|   min-width: 320px; |   min-width: 320px; | ||||||
|   min-height: 100vh; |   min-height: 100vh; | ||||||
|   display: block; |   display: block; | ||||||
|   /* color: #26201A; */ |   background-color: #ffffff; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* .MuiTypography-root { |  | ||||||
|   color: #26201A !important; |  | ||||||
| } */ |  | ||||||
|  |  | ||||||
| h1 { | h1 { | ||||||
|   font-size: 3.2em; |   font-size: 3.2em; | ||||||
|   line-height: 1.1; |   line-height: 1.1; | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
| import { Box, Button, TextField, Grid, Avatar, Typography } from '@mui/material'; | import { Box, Button, TextField, Grid, Avatar, Typography, Paper } from '@mui/material'; | ||||||
|  | import { handleDecimalInputKeyDown } from '../utils/validation'; | ||||||
|  |  | ||||||
| export default function AddOrEditProductForm({ onAdd, initialData, onCancel }) { | export default function AddOrEditProductForm({ onAdd, initialData, onCancel }) { | ||||||
|   const [product, setProduct] = useState({ |   const [product, setProduct] = useState({ | ||||||
| @@ -52,26 +53,78 @@ export default function AddOrEditProductForm({ onAdd, initialData, onCancel }) { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Box mt={2}> |     <Box sx={{ px: 2, py: 3 }}> | ||||||
|       <TextField |       <Box display="flex" flexDirection={{ xs: 'column', md: 'row' }} gap={4}> | ||||||
|         fullWidth |         {/* Left visual panel */} | ||||||
|         label="Name" |         <Paper | ||||||
|         name="name" |           elevation={0} | ||||||
|         value={product.name} |           sx={{ | ||||||
|         onChange={handleChange} |             bgcolor: '#f9f9f9', | ||||||
|         margin="normal" |             p: 2, | ||||||
|       /> |             borderRadius: 2, | ||||||
|       <TextField |             flex: 1, | ||||||
|         fullWidth |             minWidth: '400px', | ||||||
|         label="Price" |           }} | ||||||
|         name="price" |         > | ||||||
|         type="number" |           <Typography variant="subtitle1" fontWeight={600} gutterBottom> | ||||||
|         value={product.price} |             {product.name || 'N/A'} | ||||||
|         onChange={handleChange} |           </Typography> | ||||||
|         margin="normal" |           <Typography variant="body2" gutterBottom> | ||||||
|       /> |             {product.provider || 'N/A'} | ||||||
|       <Grid container spacing={2}> |           </Typography> | ||||||
|         <Grid item xs={6}> |           <Typography variant="body2" gutterBottom> | ||||||
|  |             {product.price ? `$${product.price.toFixed(2)}` : '$0.00'} | ||||||
|  |           </Typography> | ||||||
|  |           {product.representation ? ( | ||||||
|  |             <Avatar | ||||||
|  |               variant="rounded" | ||||||
|  |               src={product.representation} | ||||||
|  |               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="Product Name" | ||||||
|  |             name="name" | ||||||
|  |             value={product.name} | ||||||
|  |             onChange={handleChange} | ||||||
|  |             margin="normal" | ||||||
|  |           /> | ||||||
|  |           <TextField | ||||||
|  |             fullWidth | ||||||
|  |             label="Price" | ||||||
|  |             name="price" | ||||||
|  |             type="number" | ||||||
|  |             value={product.price} | ||||||
|  |             onChange={handleChange} | ||||||
|  |             margin="normal" | ||||||
|  |             onKeyDown={handleDecimalInputKeyDown} | ||||||
|  |           /> | ||||||
|           <TextField |           <TextField | ||||||
|             fullWidth |             fullWidth | ||||||
|             label="Provider" |             label="Provider" | ||||||
| @@ -80,8 +133,6 @@ export default function AddOrEditProductForm({ onAdd, initialData, onCancel }) { | |||||||
|             onChange={handleChange} |             onChange={handleChange} | ||||||
|             margin="normal" |             margin="normal" | ||||||
|           /> |           /> | ||||||
|         </Grid> |  | ||||||
|         <Grid item xs={6}> |  | ||||||
|           <TextField |           <TextField | ||||||
|             fullWidth |             fullWidth | ||||||
|             label="Stock" |             label="Stock" | ||||||
| @@ -90,48 +141,21 @@ export default function AddOrEditProductForm({ onAdd, initialData, onCancel }) { | |||||||
|             value={product.stock} |             value={product.stock} | ||||||
|             onChange={handleChange} |             onChange={handleChange} | ||||||
|             margin="normal" |             margin="normal" | ||||||
|  |             onKeyDown={handleDecimalInputKeyDown} | ||||||
|  |           /> | ||||||
|  |           <TextField | ||||||
|  |             fullWidth | ||||||
|  |             label="Category" | ||||||
|  |             name="category" | ||||||
|  |             value={product.category} | ||||||
|  |             onChange={handleChange} | ||||||
|  |             margin="normal" | ||||||
|           /> |           /> | ||||||
|         </Grid> |  | ||||||
|       </Grid> |  | ||||||
|       <TextField |  | ||||||
|         fullWidth |  | ||||||
|         label="Category" |  | ||||||
|         name="category" |  | ||||||
|         value={product.category} |  | ||||||
|         onChange={handleChange} |  | ||||||
|         margin="normal" |  | ||||||
|       /> |  | ||||||
|       <Grid item xs={12} textAlign="center"> |  | ||||||
|         {product.representation && ( |  | ||||||
|           <Box mb={1}> |  | ||||||
|             <Typography variant="subtitle1" gutterBottom>Representation</Typography> |  | ||||||
|             <Avatar |  | ||||||
|               variant="rounded" |  | ||||||
|               src={product.representation} |  | ||||||
|               sx={{ width: 120, height: 120, mx: 'auto' }} |  | ||||||
|               imgProps={{ |  | ||||||
|                 style: { |  | ||||||
|                   objectFit: 'contain', |  | ||||||
|                   width: '100%', |  | ||||||
|                   height: '100%', |  | ||||||
|                 } |  | ||||||
|               }} |  | ||||||
|             /> |  | ||||||
|           </Box> |  | ||||||
|         )} |  | ||||||
|         <Button component="label" className="button-transparent"> |  | ||||||
|           Upload Image |  | ||||||
|           <input type="file" hidden accept="image/*" onChange={handleImageChange} /> |  | ||||||
|         </Button> |  | ||||||
|       </Grid> |  | ||||||
|  |  | ||||||
|       <Box mt={2}> |           <Box display="flex" justifyContent="flex-end" gap={1} mt={3}> | ||||||
|         {/* Fields... */} |             <Button onClick={onCancel} className='button-transparent'>Cancel</Button> | ||||||
|         <Box display="flex" justifyContent="flex-end" gap={1} mt={2}> |             <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> | ||||||
|           <Button onClick={onCancel} className='button-transparent'> |           </Box> | ||||||
|             Cancel</Button> |  | ||||||
|           <Button variant="contained" onClick={handleSubmit} className="button-gold"> |  | ||||||
|             Save</Button> |  | ||||||
|         </Box> |         </Box> | ||||||
|       </Box> |       </Box> | ||||||
|     </Box> |     </Box> | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ import '../App.css'; | |||||||
| const columnsBase = [ | const columnsBase = [ | ||||||
|     { |     { | ||||||
|         field: 'representation', |         field: 'representation', | ||||||
|         headerName: 'Representation', |         headerName: '', | ||||||
|         flex: 2, |         flex: 2, | ||||||
|         renderCell: (params) => { |         renderCell: (params) => { | ||||||
|             const { representation, name, provider, price } = params.row; |             const { representation, name, provider, price } = params.row; | ||||||
| @@ -25,7 +25,7 @@ const columnsBase = [ | |||||||
|                         component="img" |                         component="img" | ||||||
|                         src={representation || '/favicon.png'} |                         src={representation || '/favicon.png'} | ||||||
|                         alt={name} |                         alt={name} | ||||||
|                         sx={{ width: 140, height: 140, borderRadius: 1, objectFit: 'cover' }} |                         sx={{ width: 120, height: 140, borderRadius: 1, objectFit: 'cover' }} | ||||||
|                     /> |                     /> | ||||||
|                     <Box> |                     <Box> | ||||||
|                         <Typography fontWeight={700}>{name}</Typography> |                         <Typography fontWeight={700}>{name}</Typography> | ||||||
| @@ -37,18 +37,17 @@ const columnsBase = [ | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     { field: 'company', headerName: 'Company', flex: 1 }, |     { field: 'company', headerName: 'Company', flex: 1 }, | ||||||
|     { field: 'name', headerName: 'Name', flex: 1 }, |  | ||||||
|     { field: 'stock', headerName: 'Stock', width: 120, type: 'number' }, |  | ||||||
|     { field: 'category', headerName: 'Category', flex: 1 }, |     { field: 'category', headerName: 'Category', flex: 1 }, | ||||||
|  |     { field: 'stock', headerName: 'Stock', width: 120, type: 'number' }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| export default function Admin({ children, maxWidth = 'lg', sx = {} }) { | export default function Admin({ children, maxWidth = 'lg', sx = {} }) { | ||||||
|     const [rows, setRows] = useState([ |     const [rows, setRows] = useState([ | ||||||
|         { id: 1, company: 'Fendi casa', name: 'Product 1', price: 10.99, provider: 'Provider A', stock: 100, category: 'Home', representation: '/favicon.png' }, |         { id: 1, company: 'Fendi casa', name: 'Product 1', price: 10.99, provider: 'Provider A', stock: 100, category: 'Home', representation: '/1.jpg' }, | ||||||
|         { id: 2, company: 'Fendi casa', name: 'Product 2', price: 20.0, provider: 'Provider B', stock: 50, category: 'Home', representation: '/logo.png' }, |         { id: 2, company: 'Fendi casa', name: 'Product 2', price: 20.0, provider: 'Provider B', stock: 50, category: 'Home', representation: '/2.jpg' }, | ||||||
|         { id: 3, company: 'Fendi casa', name: 'Product 3', price: 5.5, provider: 'Provider C', stock: 200, category: 'Home', representation: '/background.jpg' }, |         { id: 3, company: 'Fendi casa', name: 'Product 3', price: 5.5, provider: 'Provider C', stock: 200, category: 'Home', representation: '/3.jpg' }, | ||||||
|         { id: 4, company: 'Fendi casa', name: 'Product 4', price: 15.75, provider: 'Provider D', stock: 30, category: 'Home', representation: '/background.jpg' }, |         { id: 4, company: 'Fendi casa', name: 'Product 4', price: 15.75, provider: 'Provider D', stock: 30, category: 'Home', representation: '/4.jpg' }, | ||||||
|         { id: 5, company: 'Fendi casa', name: 'Product 5', price: 8.2, provider: 'Provider E', stock: 75, category: 'Home', representation: '/favicon.png' } |         { id: 5, company: 'Fendi casa', name: 'Product 5', price: 8.2, provider: 'Provider E', stock: 75, category: 'Home', representation: '/5.jpg' } | ||||||
|     ]); |     ]); | ||||||
|  |  | ||||||
|     const [open, setOpen] = useState(false); |     const [open, setOpen] = useState(false); | ||||||
| @@ -94,7 +93,7 @@ export default function Admin({ children, maxWidth = 'lg', sx = {} }) { | |||||||
|             renderCell: (params) => ( |             renderCell: (params) => ( | ||||||
|                 <Box display="flex" |                 <Box display="flex" | ||||||
|                     alignItems="center" |                     alignItems="center" | ||||||
|                     justifyContent="flex-start" |                     justifyContent="flex-end" | ||||||
|                     height="100%" |                     height="100%" | ||||||
|                     gap={2}> |                     gap={2}> | ||||||
|                     <IconButton |                     <IconButton | ||||||
| @@ -134,11 +133,11 @@ export default function Admin({ children, maxWidth = 'lg', sx = {} }) { | |||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
|         <SectionContainer sx={{ width: '100%' }}> |         <SectionContainer sx={{ width: '100%' }}> | ||||||
|             <Typography variant="h6" gutterBottom color='#26201AFF'> |             <Typography variant="h4" gutterBottom color='#26201AFF'> | ||||||
|                 Product Catalog |                 Product Catalog | ||||||
|             </Typography> |             </Typography> | ||||||
|  |  | ||||||
|             <Dialog open={open} onClose={() => { setOpen(false); setEditingProduct(null); }} maxWidth="sm" fullWidth> |             <Dialog open={open} onClose={() => { setOpen(false); setEditingProduct(null); }} maxWidth="md" fullWidth> | ||||||
|                 <DialogTitle>{editingProduct ? 'Edit Product' : 'Add Product'}</DialogTitle> |                 <DialogTitle>{editingProduct ? 'Edit Product' : 'Add Product'}</DialogTitle> | ||||||
|                 <DialogContent> |                 <DialogContent> | ||||||
|                     <AddOrEditProductForm onAdd={handleAddOrEditProduct} initialData={editingProduct} onCancel={() => { setOpen(false); setEditingProduct(null); }} /> |                     <AddOrEditProductForm onAdd={handleAddOrEditProduct} initialData={editingProduct} onCancel={() => { setOpen(false); setEditingProduct(null); }} /> | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								src/utils/validation.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/utils/validation.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | export function handleDecimalInputKeyDown(e) { | ||||||
|  |     const allowedKeys = ['Backspace', 'Tab', 'Delete', 'ArrowLeft', 'ArrowRight']; | ||||||
|  |     const isDigit = /^[0-9]$/.test(e.key); | ||||||
|  |     const isDot = e.key === '.'; | ||||||
|  |  | ||||||
|  |     const currentValue = e.target.value ?? ''; | ||||||
|  |     const alreadyHasDot = currentValue.includes('.'); | ||||||
|  |  | ||||||
|  |     if (allowedKeys.includes(e.key)) return; | ||||||
|  |     if (isDot && !alreadyHasDot) return; | ||||||
|  |     if (!isDigit) e.preventDefault(); | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Rodolfo Ruiz
					Rodolfo Ruiz