diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..d1cf990
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,6 @@
+node_modules
+build
+.dockerignore
+Dockerfile
+*.md
+.git
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e4466a7
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+# Step 1: Build the React app
+FROM node:18-alpine AS build
+
+WORKDIR /app
+COPY package*.json ./
+RUN npm install
+COPY . .
+RUN npm run build
+
+# Step 2: Serve with NGINX
+FROM nginx:stable-alpine
+
+COPY --from=build /app/dist /usr/share/nginx/html
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..a6b172c
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,6 @@
+version: '3'
+services:
+ web:
+ build: .
+ ports:
+ - "3000:80"
\ No newline at end of file
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..6f6f93f
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,12 @@
+server {
+ listen 80;
+ server_name localhost;
+
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ try_files $uri /index.html;
+ }
+
+ error_page 404 /index.html;
+}
\ No newline at end of file
diff --git a/src/App.css b/src/App.css
index 73c7a9c..d54ede8 100644
--- a/src/App.css
+++ b/src/App.css
@@ -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);
}
diff --git a/src/App.jsx b/src/App.jsx
index d57c311..c3678f2 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -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' && }
{zone === 'public' && currentView === 'Clients' && }
- {zone === 'public' && currentView === 'Providers' && }
+ {zone === 'public' && currentView === 'Providers' && }
+ {zone === 'public' && currentView === 'Categories' && }
diff --git a/src/components/MenuDrawer.jsx b/src/components/MenuDrawer.jsx
index ce52d68..c0518ed 100644
--- a/src/components/MenuDrawer.jsx
+++ b/src/components/MenuDrawer.jsx
@@ -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: },
+ { text: 'Clients', icon: },
+ { text: 'Products', icon: },
+ { text: 'Providers', icon: },
+ { text: 'Logout', icon: },
+ ],
+ restricted: [],
+ private: [
+ { text: 'Categories', icon: },
+ { text: 'Clients', icon: },
+ { text: 'Products', icon: },
+ { text: 'Providers', icon: },
+ { text: 'Logout', icon: },
+ ],
};
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'
},
},
}}>
-
- {items.map((text, index) => (
+
+
+ Fendi Casa
+ Administrator
+
+
+ {items.map(({ text, icon }, index) => (
{
onClose(); // Close drawer
onSelect?.(text); // Notify parent of selected item
}}>
+
+ {icon}
);
-}
\ No newline at end of file
+}
diff --git a/src/private/categories/AddOrEditCategoryForm.jsx b/src/private/categories/AddOrEditCategoryForm.jsx
new file mode 100644
index 0000000..e86a2d6
--- /dev/null
+++ b/src/private/categories/AddOrEditCategoryForm.jsx
@@ -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 (
+
+
+
+ Category Details
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/private/categories/Categories.jsx b/src/private/categories/Categories.jsx
new file mode 100644
index 0000000..3b2fba3
--- /dev/null
+++ b/src/private/categories/Categories.jsx
@@ -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) => (
+
+ handleEditClick(params)}
+ >
+
+
+ handleDeleteClick(params.row)}
+ >
+
+
+
+ )
+ }
+ ];
+
+ return (
+
+
+ Categories
+
+
+
+
+
+
+
+ ({ top: 8, bottom: 8 })}
+ />
+
+
+
+
+
+
+ );
+}
diff --git a/src/theme.jsx b/src/theme.jsx
index 57f793f..a5d5fc6 100644
--- a/src/theme.jsx
+++ b/src/theme.jsx
@@ -66,7 +66,7 @@ const theme = createTheme({
root: {
transition: 'background-color 0.2s ease-in-out',
'&:hover': {
- backgroundColor: '#AA7665FF',
+ backgroundColor: '#AA7665FF',
},
},
},