Compare commits
6 Commits
98f6f9814d
...
0d63977b21
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d63977b21 | ||
|
|
ee79740d8d | ||
|
|
70afbd4b45 | ||
|
|
cef04888c6 | ||
|
|
083b077ebb | ||
|
|
b3f939ccdc |
75
package-lock.json
generated
75
package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"@fontsource/roboto": "^5.2.5",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/x-data-grid": "^8.5.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
@@ -1429,6 +1430,65 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.5.0.tgz",
|
||||
"integrity": "sha512-5rrMm9anFaLk9O5XRIw3J9tAAnaiE1GxFeocyqhDj23RUReMg0YSp3FYnCaFLAehRQVgT9pC4675XO571paxKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/utils": "^7.0.2",
|
||||
"@mui/x-internals": "8.5.0",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^5.1.1",
|
||||
"use-sync-external-store": "^1.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-internals": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.5.0.tgz",
|
||||
"integrity": "sha512-Ef4KJij1pBGk6/xILyVZHf76tcuRpJIX30k4Ghklsd5QJujZ9ENCGAjvd7aWRAFAs5p3ffn0H8UDESoIcroj1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/utils": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
@@ -3785,6 +3845,12 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
@@ -4239,6 +4305,15 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@fontsource/roboto": "^5.2.5",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/x-data-grid": "^8.5.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
|
||||
12
src/App.jsx
12
src/App.jsx
@@ -1,9 +1,12 @@
|
||||
import { useState } from 'react'
|
||||
import Background from "./components/Background";
|
||||
import VideoBackground from "./components/VimeoEmbed";
|
||||
import AppHeader from './components/AppHeader';
|
||||
import Footer from './components/Footer';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import Admin from './private/Admin';
|
||||
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
@@ -12,7 +15,8 @@ function App() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<Background imageName='background.jpg' opacity={0.65} />
|
||||
{/* <Background imageName='background.jpg' opacity={0.65} /> */}
|
||||
<VideoBackground videoId="1066622045" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
@@ -26,9 +30,9 @@ function App() {
|
||||
|
||||
{/* Main content area */}
|
||||
<Box component="main" sx={{ flex: 1, p: 2 }}>
|
||||
<h1>Welcome to the Fendi Casa Experience</h1>
|
||||
<p>This is a sample box.</p>
|
||||
|
||||
{zone === 'private' && <Admin />}
|
||||
{zone === 'restricted' && <Admin />}
|
||||
{zone === 'public' && <Admin />}
|
||||
</Box>
|
||||
<Footer zone={zone} />
|
||||
</Box>
|
||||
|
||||
@@ -67,7 +67,7 @@ export default function AppHeader({ zone = 'public' }) {
|
||||
)}
|
||||
|
||||
{/* Rendering the Drawer */}
|
||||
<MenuDrawer zone={zone} open={menuOpen} onClose={() => setMenuOpen(false)} />
|
||||
<MenuDrawer zone='private' open={menuOpen} onClose={() => setMenuOpen(false)} />
|
||||
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function MenuDrawer({ zone = 'public', open, onClose }) {
|
||||
<Drawer anchor="left" open={open} onClose={onClose} slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
backgroundColor: '#000000a0', // negro semitransparente
|
||||
backgroundColor: '#000000a0',
|
||||
width: isMobile ? '100vw' : 250,
|
||||
},
|
||||
},
|
||||
|
||||
18
src/components/SectionContainer.jsx
Normal file
18
src/components/SectionContainer.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
export default function SectionContainer({ children, maxWidth = 'lg', sx = {} }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxWidth,
|
||||
mx: 'auto',
|
||||
px: { xs: 2, md: 4 },
|
||||
py: { xs: 3, md: 5 },
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
100
src/private/AddOrEditProductForm.jsx
Normal file
100
src/private/AddOrEditProductForm.jsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Button, TextField, Grid } from '@mui/material';
|
||||
|
||||
export default function AddOrEditProductForm({ onAdd, initialData, onCancel }) {
|
||||
const [product, setProduct] = useState({
|
||||
name: '',
|
||||
price: '',
|
||||
provider: '',
|
||||
stock: '',
|
||||
category: ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
setProduct(initialData);
|
||||
} else {
|
||||
setProduct({
|
||||
name: '',
|
||||
price: '',
|
||||
provider: '',
|
||||
stock: '',
|
||||
category: ''
|
||||
});
|
||||
}
|
||||
}, [initialData]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setProduct((prev) => ({
|
||||
...prev,
|
||||
[name]: name === 'price' || name === 'stock' ? Number(value) : value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (onAdd) {
|
||||
onAdd(product);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mt={2}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="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"
|
||||
/>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Provider"
|
||||
name="provider"
|
||||
value={product.provider}
|
||||
onChange={handleChange}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Stock"
|
||||
name="stock"
|
||||
type="number"
|
||||
value={product.stock}
|
||||
onChange={handleChange}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Category"
|
||||
name="category"
|
||||
value={product.category}
|
||||
onChange={handleChange}
|
||||
margin="normal"
|
||||
/>
|
||||
<Box mt={2}>
|
||||
{/* Fields... */}
|
||||
<Box display="flex" justifyContent="flex-end" gap={1} mt={2}>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<Button variant="contained" onClick={handleSubmit}>Save</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
126
src/private/Admin.jsx
Normal file
126
src/private/Admin.jsx
Normal file
@@ -0,0 +1,126 @@
|
||||
|
||||
import SectionContainer from '../components/SectionContainer';
|
||||
import React, { useState } from 'react';
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import { Typography, Button, Dialog, DialogTitle, DialogContent, IconButton, Box } from '@mui/material';
|
||||
import AddOrEditProductForm from './AddOrEditProductForm.jsx';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
|
||||
const columnsBase = [
|
||||
{ field: 'id', headerName: 'ID', width: 70 },
|
||||
{ field: 'company', headerName: 'Company', flex: 1 },
|
||||
{ field: 'name', headerName: 'Name', flex: 1 },
|
||||
{ field: 'price', headerName: '$', width: 100, type: 'number' },
|
||||
{ field: 'provider', headerName: 'Provider', flex: 1 },
|
||||
{ field: 'stock', headerName: 'Stock', width: 100, type: 'number' },
|
||||
{ field: 'category', headerName: 'Category', flex: 1 }
|
||||
];
|
||||
|
||||
export default function Admin({ children, maxWidth = 'lg', sx = {} }) {
|
||||
const [rows, setRows] = useState([
|
||||
{ id: 1, company: 'Fendi casa', name: 'Product 1', price: 10.99, provider: 'Provider A', stock: 100, category: 'Home' },
|
||||
{ id: 2, company: 'Fendi casa', name: 'Product 2', price: 20.0, provider: 'Provider B', stock: 50, category: 'Home' },
|
||||
{ id: 3, company: 'Fendi casa', name: 'Product 3', price: 5.5, provider: 'Provider C', stock: 200, category: 'Home' },
|
||||
{ id: 4, company: 'Fendi casa', name: 'Product 4', price: 15.75, provider: 'Provider D', stock: 30, category: 'Home' },
|
||||
{ id: 5, company: 'Fendi casa', name: 'Product 5', price: 8.2, provider: 'Provider E', stock: 75, category: 'Home' }
|
||||
]);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [editingProduct, setEditingProduct] = useState(null);
|
||||
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [rowToDelete, setRowToDelete] = useState(null);
|
||||
|
||||
const handleAddOrEditProduct = (product) => {
|
||||
if (editingProduct) {
|
||||
// Update existing
|
||||
setRows(rows.map((row) => (row.id === editingProduct.id ? { ...editingProduct, ...product } : row)));
|
||||
} else {
|
||||
// Add new
|
||||
const id = rows.length + 1;
|
||||
setRows([...rows, { id, company: 'Fendi casa', ...product }]);
|
||||
}
|
||||
setOpen(false);
|
||||
setEditingProduct(null);
|
||||
};
|
||||
|
||||
const handleEditClick = (params) => {
|
||||
setEditingProduct(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: 'Actions',
|
||||
width: 130,
|
||||
renderCell: (params) => (
|
||||
<Box>
|
||||
<IconButton color="primary" onClick={() => handleEditClick(params)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton color="error" onClick={() => handleDeleteClick(params.row)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<SectionContainer sx={{ width: '100%' }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Product Catalog
|
||||
</Typography>
|
||||
|
||||
<Dialog open={open} onClose={() => { setOpen(false); setEditingProduct(null); }} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>{editingProduct ? 'Edit Product' : 'Add Product'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<AddOrEditProductForm onAdd={handleAddOrEditProduct} initialData={editingProduct} onCancel={() => { setOpen(false); setEditingProduct(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)}>Cancel</Button>
|
||||
<Button variant="contained" color="error" onClick={confirmDelete}>Delete</Button>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Box mt={2}>
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
pageSize={5}
|
||||
rowsPerPageOptions={[5]}
|
||||
/>
|
||||
|
||||
<Box display="flex" justifyContent="flex-end" mt={2}>
|
||||
<Button variant="contained" color="primary" onClick={() => setOpen(true)}>
|
||||
Add Product
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user