feat: added categories
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								public/logo.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/logo.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 3.3 KiB | 
							
								
								
									
										15
									
								
								src/App.jsx
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								src/App.jsx
									
									
									
									
									
								
							| @@ -7,6 +7,7 @@ import Footer from './components/Footer'; | |||||||
| import Dashboard from './private/dashboard/Dashboard'; | import Dashboard from './private/dashboard/Dashboard'; | ||||||
| import UserManagement from './private/users/UserManagement'; | import UserManagement from './private/users/UserManagement'; | ||||||
| import FurnitureVariantManagement from './private/fornitures/FurnitureVariantManagement'; | import FurnitureVariantManagement from './private/fornitures/FurnitureVariantManagement'; | ||||||
|  | import Categories from './private/categories/Categories'; | ||||||
| import LoginPage from './private/LoginPage'; | import LoginPage from './private/LoginPage'; | ||||||
| import { Routes, Route, Navigate } from 'react-router-dom'; | import { Routes, Route, Navigate } from 'react-router-dom'; | ||||||
| import { useAuth } from './context/AuthContext'; | import { useAuth } from './context/AuthContext'; | ||||||
| @@ -24,10 +25,8 @@ export default function App() { | |||||||
|  |  | ||||||
|   const mainLeft = isMobile ? 0 : (drawerExpanded ? DRAWER_EXPANDED : DRAWER_COLLAPSED); |   const mainLeft = isMobile ? 0 : (drawerExpanded ? DRAWER_EXPANDED : DRAWER_COLLAPSED); | ||||||
|  |  | ||||||
|   // Evita flicker mientras se restaura sesión |  | ||||||
|   if (initializing) return null; |   if (initializing) return null; | ||||||
|  |  | ||||||
|   // === RUTAS PÚBLICAS (sin shell) === |  | ||||||
|   if (!user) { |   if (!user) { | ||||||
|     return ( |     return ( | ||||||
|       <Routes> |       <Routes> | ||||||
| @@ -37,14 +36,9 @@ export default function App() { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // === RUTAS PRIVADAS (con shell) === |  | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <AppHeader |       <AppHeader zone="private" currentPage={currentView} leftOffset={mainLeft} /> | ||||||
|         zone="private" |  | ||||||
|         currentPage={currentView} |  | ||||||
|         leftOffset={mainLeft} |  | ||||||
|       /> |  | ||||||
|  |  | ||||||
|       <MenuDrawerPrivate |       <MenuDrawerPrivate | ||||||
|         onSelect={(value) => setCurrentView(value)} |         onSelect={(value) => setCurrentView(value)} | ||||||
| @@ -64,9 +58,7 @@ export default function App() { | |||||||
|         }} |         }} | ||||||
|       > |       > | ||||||
|         <Routes> |         <Routes> | ||||||
|           {/* Si ya está autenticado, /login redirige al dashboard */} |  | ||||||
|           <Route path="/login" element={<Navigate to="/" replace />} /> |           <Route path="/login" element={<Navigate to="/" replace />} /> | ||||||
|  |  | ||||||
|           <Route |           <Route | ||||||
|             path="/" |             path="/" | ||||||
|             element={ |             element={ | ||||||
| @@ -76,6 +68,9 @@ export default function App() { | |||||||
|                 {currentView === '/ProductsManagement/CatalogManagement/ProductCollections' && ( |                 {currentView === '/ProductsManagement/CatalogManagement/ProductCollections' && ( | ||||||
|                   <FurnitureVariantManagement /> |                   <FurnitureVariantManagement /> | ||||||
|                 )} |                 )} | ||||||
|  |                 {currentView === '/ProductsManagement/CatalogManagement/Categories' && ( | ||||||
|  |                   <Categories /> | ||||||
|  |                 )} | ||||||
|               </> |               </> | ||||||
|             } |             } | ||||||
|           /> |           /> | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								src/api/CategoriesApi.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/api/CategoriesApi.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | // src/api/CategoriesApi.js | ||||||
|  | export default class CategoriesApi { | ||||||
|  |   constructor(token) { | ||||||
|  |     this.baseUrl = 'https://inventory-bff.dream-views.com/api/v1/Tags'; | ||||||
|  |     this.token = token; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   headers(json = true) { | ||||||
|  |     return { | ||||||
|  |       accept: 'application/json', | ||||||
|  |       ...(json ? { 'Content-Type': 'application/json' } : {}), | ||||||
|  |       ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async getAll() { | ||||||
|  |     const res = await fetch(`${this.baseUrl}/GetAll`, { | ||||||
|  |       method: 'GET', | ||||||
|  |       headers: this.headers(false), | ||||||
|  |     }); | ||||||
|  |     if (!res.ok) throw new Error(`GetAll error ${res.status}: ${await res.text()}`); | ||||||
|  |     return res.json(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async create(payload) { | ||||||
|  |     const res = await fetch(`${this.baseUrl}/Create`, { | ||||||
|  |       method: 'POST', | ||||||
|  |       headers: this.headers(), | ||||||
|  |       body: JSON.stringify(payload), | ||||||
|  |     }); | ||||||
|  |     if (!res.ok) throw new Error(`Create error ${res.status}: ${await res.text()}`); | ||||||
|  |     return res.json(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async update(payload) { | ||||||
|  |     const res = await fetch(`${this.baseUrl}/Update`, { | ||||||
|  |       method: 'PUT', | ||||||
|  |       headers: this.headers(), | ||||||
|  |       body: JSON.stringify(payload), | ||||||
|  |     }); | ||||||
|  |     if (!res.ok) throw new Error(`Update error ${res.status}: ${await res.text()}`); | ||||||
|  |     return res.json(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async delete(payload) { | ||||||
|  |     const res = await fetch(`${this.baseUrl}/Delete`, { | ||||||
|  |       method: 'DELETE', | ||||||
|  |       headers: this.headers(), | ||||||
|  |       body: JSON.stringify(payload), | ||||||
|  |     }); | ||||||
|  |     if (!res.ok) throw new Error(`Delete error ${res.status}: ${await res.text()}`); | ||||||
|  |     return res.json(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -40,11 +40,13 @@ const menuData = [ | |||||||
|                             { title: 'Categories' } |                             { title: 'Categories' } | ||||||
|                         ] |                         ] | ||||||
|                     }, |                     }, | ||||||
|                     { title: 'Products', |                     { | ||||||
|                     children: [ |                         title: 'Products', | ||||||
|                         { title: 'AR  Assets Library Management' }, |                         children: [ | ||||||
|                         { title: 'Media Management' }, |                             { title: 'AR  Assets Library Management' }, | ||||||
|                     ] }, |                             { title: 'Media Management' }, | ||||||
|  |                         ] | ||||||
|  |                     }, | ||||||
|                     { title: 'Product Collections' }, |                     { title: 'Product Collections' }, | ||||||
|                 ] |                 ] | ||||||
|             } |             } | ||||||
| @@ -54,13 +56,14 @@ const menuData = [ | |||||||
|         title: 'Customers', |         title: 'Customers', | ||||||
|         icon: <PeopleAltIcon />, |         icon: <PeopleAltIcon />, | ||||||
|         children: [ |         children: [ | ||||||
|             { title: 'CRM', |             { | ||||||
|  |                 title: 'CRM', | ||||||
|                 children: [ |                 children: [ | ||||||
|                      { title: 'Customer List' }, |                     { title: 'Customer List' }, | ||||||
|                       { title: 'Projects' }, |                     { title: 'Projects' }, | ||||||
|                     { title: 'Customer Collections' }, |                     { title: 'Customer Collections' }, | ||||||
|                 ] |                 ] | ||||||
|              }, |             }, | ||||||
|  |  | ||||||
|             { |             { | ||||||
|                 title: 'Sales', |                 title: 'Sales', | ||||||
| @@ -86,12 +89,13 @@ const menuData = [ | |||||||
|         icon: <AdminPanelSettingsIcon />, |         icon: <AdminPanelSettingsIcon />, | ||||||
|         children: [ |         children: [ | ||||||
|             { title: 'Users Management' }, |             { title: 'Users Management' }, | ||||||
|             { title: 'Access Control', |             { | ||||||
|  |                 title: 'Access Control', | ||||||
|                 children: [ |                 children: [ | ||||||
|                     { title: 'Roles' }, |                     { title: 'Roles' }, | ||||||
|                     { title: 'Permissions' }, |                     { title: 'Permissions' }, | ||||||
|                 ] |                 ] | ||||||
|              }, |             }, | ||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
| @@ -157,6 +161,8 @@ export default function MenuDrawerPrivate({ | |||||||
|                                     onSelect?.('/Users/UserManagement'); |                                     onSelect?.('/Users/UserManagement'); | ||||||
|                                 } else if (node.title === 'Product Collections') { |                                 } else if (node.title === 'Product Collections') { | ||||||
|                                     onSelect?.('/ProductsManagement/CatalogManagement/ProductCollections'); |                                     onSelect?.('/ProductsManagement/CatalogManagement/ProductCollections'); | ||||||
|  |                                 } else if (node.title === 'Categories') { | ||||||
|  |                                     onSelect?.('/ProductsManagement/CatalogManagement/Categories'); | ||||||
|                                 } else { |                                 } else { | ||||||
|                                     onSelect?.(node.title); |                                     onSelect?.(node.title); | ||||||
|                                 } |                                 } | ||||||
| @@ -199,7 +205,7 @@ export default function MenuDrawerPrivate({ | |||||||
|                 {hasChildren && !collapsed && ( |                 {hasChildren && !collapsed && ( | ||||||
|                     <Collapse in={!!openMap[key]} timeout="auto" unmountOnExit> |                     <Collapse in={!!openMap[key]} timeout="auto" unmountOnExit> | ||||||
|                         <List component="div" disablePadding sx={{ pl: 7 }}> |                         <List component="div" disablePadding sx={{ pl: 7 }}> | ||||||
|                             {node.children.map((child, idx) => renderNode(child, `${key}-`))} |                             {node.children.map((child) => renderNode(child, `${key}-`))} | ||||||
|                         </List> |                         </List> | ||||||
|                     </Collapse> |                     </Collapse> | ||||||
|                 )} |                 )} | ||||||
|   | |||||||
| @@ -1,64 +1,94 @@ | |||||||
| import { useState, useEffect } from 'react'; | import { useEffect, useMemo, useState } from 'react'; | ||||||
| import { Box, Button, TextField, Typography, Paper } from '@mui/material'; | import { Box, Button, Paper, TextField, Typography } from '@mui/material'; | ||||||
|  | import { useAuth } from '../../context/AuthContext'; | ||||||
|  | import CategoriesApi from '../../api/CategoriesApi'; | ||||||
|  |  | ||||||
| export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) { | export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) { | ||||||
|     const [category, setCategory] = useState({ |   const { user } = useAuth(); | ||||||
|         name: '', |   const token = user?.thalosToken || localStorage.getItem('thalosToken'); | ||||||
|         description: '' |   const api = useMemo(() => new CategoriesApi(token), [token]); | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     useEffect(() => { |   const [category, setCategory] = useState({ | ||||||
|         if (initialData) { |     _Id: '', | ||||||
|             setCategory(initialData); |     id: '', | ||||||
|         } else { |     name: '', | ||||||
|             setCategory({ name: '', description: '' }); |     description: '', | ||||||
|         } |     status: 'Active', | ||||||
|     }, [initialData]); |   }); | ||||||
|  |  | ||||||
|     const handleChange = (e) => { |   useEffect(() => { | ||||||
|         const { name, value } = e.target; |     if (initialData) { | ||||||
|         setCategory((prev) => ({ ...prev, [name]: value })); |       setCategory({ | ||||||
|     }; |         _Id: initialData._id || initialData._Id || '', | ||||||
|  |         id: initialData.id || initialData.Id || initialData._id || initialData._Id || '', | ||||||
|  |         name: initialData.name ?? '', | ||||||
|  |         description: initialData.description ?? '', | ||||||
|  |         status: initialData.status ?? 'Active', | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       setCategory({ _Id: '', id: '', name: '', description: '', status: 'Active' }); | ||||||
|  |     } | ||||||
|  |   }, [initialData]); | ||||||
|  |  | ||||||
|     const handleSubmit = () => { |   const handleChange = (e) => { | ||||||
|         if (onAdd) { |     const { name, value } = e.target; | ||||||
|             onAdd(category); |     setCategory((prev) => ({ ...prev, [name]: value })); | ||||||
|         } |   }; | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     return ( |   const handleSubmit = async () => { | ||||||
|         <Box sx={{ px: 2, py: 3 }}> |     try { | ||||||
|             <Paper elevation={0} sx={{ p: 3, bgcolor: '#f9f9f9', borderRadius: 2 }}> |       if (category._Id) { | ||||||
|                 <Typography variant="h6" gutterBottom> |         const payload = { | ||||||
|                     Category Details |           _Id: category._Id, | ||||||
|                 </Typography> |           Id: category.id || category._Id, | ||||||
|                 <TextField |           name: category.name, | ||||||
|                     fullWidth |           description: category.description, | ||||||
|                     label="Name" |           status: category.status, | ||||||
|                     name="name" |         }; | ||||||
|                     value={category.name} |         await api.update(payload); | ||||||
|                     onChange={handleChange} |       } else { | ||||||
|                     margin="normal" |         const payload = { | ||||||
|                 /> |           name: category.name, | ||||||
|                 <TextField |           description: category.description, | ||||||
|                     fullWidth |           status: category.status, | ||||||
|                     label="Description" |         }; | ||||||
|                     name="description" |         await api.create(payload); | ||||||
|                     value={category.description} |       } | ||||||
|                     onChange={handleChange} |       onAdd?.(); | ||||||
|                     margin="normal" |     } catch (e) { | ||||||
|                     multiline |       console.error('Submit category failed:', e); | ||||||
|                     rows={4} |     } | ||||||
|                 /> |   }; | ||||||
|                 <Box display="flex" justifyContent="flex-end" gap={1} mt={3}> |  | ||||||
|                     <Button onClick={onCancel} className="button-transparent"> |   return ( | ||||||
|                         Cancel |     <Paper sx={{ p: 2 }}> | ||||||
|                     </Button> |       <Typography variant="subtitle1" sx={{ mb: 2 }}> | ||||||
|                     <Button variant="contained" onClick={handleSubmit} className="button-gold"> |         {category._Id ? 'Edit Category' : 'Add Category'} | ||||||
|                         Save |       </Typography> | ||||||
|                     </Button> |  | ||||||
|                 </Box> |       <TextField | ||||||
|             </Paper> |         name="name" | ||||||
|         </Box> |         label="Name" | ||||||
|     ); |         value={category.name} | ||||||
|  |         onChange={handleChange} | ||||||
|  |         fullWidth | ||||||
|  |         sx={{ mb: 2 }} | ||||||
|  |       /> | ||||||
|  |  | ||||||
|  |       <TextField | ||||||
|  |         name="description" | ||||||
|  |         label="Description" | ||||||
|  |         value={category.description} | ||||||
|  |         onChange={handleChange} | ||||||
|  |         fullWidth | ||||||
|  |         multiline | ||||||
|  |         minRows={3} | ||||||
|  |       /> | ||||||
|  |  | ||||||
|  |       <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> | ||||||
|  |   ); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,142 +1,146 @@ | |||||||
| import SectionContainer from '../../components/SectionContainer.jsx'; | import { useEffect, useMemo, useRef, useState } from 'react'; | ||||||
| import { useState } from 'react'; | import { Box, Button, Dialog, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material'; | ||||||
| import { DataGrid } from '@mui/x-data-grid'; | import { DataGrid } from '@mui/x-data-grid'; | ||||||
| import { Typography, Button, Dialog, DialogTitle, DialogContent, IconButton, Box } from '@mui/material'; | import EditIcon from '@mui/icons-material/Edit'; | ||||||
| import AddOrEditCategoryForm from './AddOrEditCategoryForm.jsx'; | import DeleteIcon from '@mui/icons-material/Delete'; | ||||||
|  | import AddOrEditCategoryForm from './AddOrEditCategoryForm'; | ||||||
|  | import CategoriesApi from '../../api/CategoriesApi'; | ||||||
|  | import { useAuth } from '../../context/AuthContext'; | ||||||
|  |  | ||||||
| import EditRoundedIcon from '@mui/icons-material/EditRounded'; | export default function Categories() { | ||||||
| import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; |   const { user } = useAuth(); | ||||||
| import '../../App.css'; |   const token = user?.thalosToken || localStorage.getItem('thalosToken'); | ||||||
|  |   const api = useMemo(() => new CategoriesApi(token), [token]); | ||||||
|  |  | ||||||
| const columnsBase = [ |   const [rows, setRows] = useState([]); | ||||||
|     { field: 'name', headerName: 'Name', flex: 1 }, |   const [open, setOpen] = useState(false); | ||||||
|     { field: 'description', headerName: 'Description', flex: 2 } |   const [editingCategory, setEditingCategory] = useState(null); | ||||||
| ]; |   const [confirmOpen, setConfirmOpen] = useState(false); | ||||||
|  |   const [rowToDelete, setRowToDelete] = useState(null); | ||||||
|  |   const hasLoaded = useRef(false); | ||||||
|  |  | ||||||
| export default function Categories({ children, maxWidth = 'lg', sx = {} }) { |   useEffect(() => { | ||||||
|     const [rows, setRows] = useState([ |     if (!hasLoaded.current) { | ||||||
|         { id: 1, name: 'Fabrics', description: 'Textile materials including silk, cotton, and synthetics.' }, |       loadData(); | ||||||
|         { id: 2, name: 'Leather Goods', description: 'Leather-based components for luxury goods.' }, |       hasLoaded.current = true; | ||||||
|         { 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 loadData = async () => { | ||||||
|     const [editingCategory, setEditingCategory] = useState(null); |     try { | ||||||
|     const [confirmOpen, setConfirmOpen] = useState(false); |       const data = await api.getAll(); | ||||||
|     const [rowToDelete, setRowToDelete] = useState(null); |       setRows(Array.isArray(data) ? data : []); | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error('Failed to load categories:', e); | ||||||
|  |       setRows([]); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|     const handleAddOrEditCategory = (category) => { |   const handleAddClick = () => { | ||||||
|         if (editingCategory) { |     setEditingCategory(null); | ||||||
|             setRows(rows.map((row) => (row.id === editingCategory.id ? { ...editingCategory, ...category } : row))); |     setOpen(true); | ||||||
|         } else { |   }; | ||||||
|             const id = rows.length + 1; |  | ||||||
|             setRows([...rows, { id, ...category }]); |  | ||||||
|         } |  | ||||||
|         setOpen(false); |  | ||||||
|         setEditingCategory(null); |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     const handleEditClick = (params) => { |   const handleEditClick = (params) => { | ||||||
|         setEditingCategory(params.row); |     const r = params?.row; | ||||||
|         setOpen(true); |     if (!r) return; | ||||||
|     }; |     setEditingCategory({ | ||||||
|  |       _Id: r._id || r._Id || '', | ||||||
|  |       id: r.id || r.Id || '', | ||||||
|  |       name: r.name ?? '', | ||||||
|  |       description: r.description ?? '', | ||||||
|  |       status: r.status ?? 'Active', | ||||||
|  |     }); | ||||||
|  |     setOpen(true); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|     const handleDeleteClick = (row) => { |   const handleDeleteClick = (row) => { | ||||||
|         setRowToDelete(row); |     setRowToDelete(row); | ||||||
|         setConfirmOpen(true); |     setConfirmOpen(true); | ||||||
|     }; |   }; | ||||||
|  |  | ||||||
|     const confirmDelete = () => { |   const confirmDelete = async () => { | ||||||
|         setRows(rows.filter((row) => row.id !== rowToDelete.id)); |     try { | ||||||
|         setRowToDelete(null); |       if (!rowToDelete) return; | ||||||
|         setConfirmOpen(false); |       const payload = { | ||||||
|     }; |         _Id: rowToDelete._id || rowToDelete._Id, | ||||||
|  |         id: rowToDelete.id || rowToDelete.Id || '', | ||||||
|  |         name: rowToDelete.name, | ||||||
|  |         description: rowToDelete.description, | ||||||
|  |         status: 'Inactive', // soft-delete | ||||||
|  |       }; | ||||||
|  |       await api.update(payload); | ||||||
|  |       await loadData(); | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error('Delete failed:', e); | ||||||
|  |     } finally { | ||||||
|  |       setConfirmOpen(false); | ||||||
|  |       setRowToDelete(null); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|     const columns = [ |   const handleFormDone = async () => { | ||||||
|         ...columnsBase, |     await loadData(); | ||||||
|         { |     setOpen(false); | ||||||
|             field: 'actions', |     setEditingCategory(null); | ||||||
|             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 ( |   const columns = [ | ||||||
|         <SectionContainer sx={{ width: '100%' }}> |     { field: 'name', headerName: 'Name', flex: 1, minWidth: 200 }, | ||||||
|             <Typography variant="h4" gutterBottom color='#26201AFF'> |     { field: 'description', headerName: 'Description', flex: 1, minWidth: 250 }, | ||||||
|                 Categories |     { field: 'status', headerName: 'Status', width: 140, valueGetter: (p) => p.row?.status ?? 'Active' }, | ||||||
|             </Typography> |     { | ||||||
|  |       field: 'actions', | ||||||
|  |       headerName: '', | ||||||
|  |       width: 120, | ||||||
|  |       sortable: false, | ||||||
|  |       filterable: false, | ||||||
|  |       renderCell: (params) => ( | ||||||
|  |         <Box sx={{ display: 'flex', gap: 1 }}> | ||||||
|  |           <IconButton size="small" onClick={() => handleEditClick(params)}><EditIcon /></IconButton> | ||||||
|  |           <IconButton size="small" color="error" onClick={() => handleDeleteClick(params.row)}><DeleteIcon /></IconButton> | ||||||
|  |         </Box> | ||||||
|  |       ), | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|             <Dialog open={open} onClose={() => { setOpen(false); setEditingCategory(null); }} fullWidth> |   return ( | ||||||
|                 <DialogTitle>{editingCategory ? 'Edit Category' : 'Add Category'}</DialogTitle> |     <Box> | ||||||
|                 <DialogContent> |       <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> | ||||||
|                     <AddOrEditCategoryForm onAdd={handleAddOrEditCategory} initialData={editingCategory} onCancel={() => { setOpen(false); setEditingCategory(null); }} /> |         <Typography variant="h6">Categories</Typography> | ||||||
|                 </DialogContent> |         <Button variant="contained" onClick={handleAddClick} className="button-gold">Add Category</Button> | ||||||
|             </Dialog> |       </Box> | ||||||
|  |  | ||||||
|             <Dialog open={confirmOpen} onClose={() => setConfirmOpen(false)}> |       <DataGrid | ||||||
|                 <DialogTitle>Confirm Delete</DialogTitle> |         rows={rows} | ||||||
|                 <DialogContent> |         columns={columns} | ||||||
|                     <Typography> |         pageSize={10} | ||||||
|                         Are you sure you want to delete <strong>{rowToDelete?.name}</strong>? |         rowsPerPageOptions={[10]} | ||||||
|                     </Typography> |         autoHeight | ||||||
|                     <Box mt={2} display="flex" justifyContent="flex-end" gap={1}> |         disableColumnMenu | ||||||
|                         <Button onClick={() => setConfirmOpen(false)} className='button-transparent'>Cancel</Button> |         getRowId={(r) => r._id || r._Id || r.id || r.Id} | ||||||
|                         <Button variant="contained" onClick={confirmDelete} className="button-gold">Delete</Button> |       /> | ||||||
|                     </Box> |  | ||||||
|                 </DialogContent> |  | ||||||
|             </Dialog> |  | ||||||
|  |  | ||||||
|             <Box mt={2}> |       <Dialog open={open} onClose={() => { setOpen(false); setEditingCategory(null); }} fullWidth> | ||||||
|                 <DataGrid |         <DialogTitle>{editingCategory ? 'Edit Category' : 'Add Category'}</DialogTitle> | ||||||
|                     rows={rows} |         <DialogContent> | ||||||
|                     columns={columns} |           <AddOrEditCategoryForm | ||||||
|                     pageSize={5} |             initialData={editingCategory} | ||||||
|                     rowsPerPageOptions={[5]} |             onAdd={handleFormDone} | ||||||
|                     getRowSpacing={() => ({ top: 8, bottom: 8 })} |             onCancel={() => { setOpen(false); setEditingCategory(null); }} | ||||||
|                 /> |           /> | ||||||
|  |         </DialogContent> | ||||||
|  |       </Dialog> | ||||||
|  |  | ||||||
|                 <Box display="flex" justifyContent="flex-end" mt={2}> |       <Dialog open={confirmOpen} onClose={() => setConfirmOpen(false)}> | ||||||
|                     <Button variant="contained" onClick={() => setOpen(true)} className="button-gold"> |         <DialogTitle>Delete Category</DialogTitle> | ||||||
|                         Add Category |         <DialogContent> | ||||||
|                     </Button> |           <Box sx={{ display: 'flex', gap: 1, mt: 2, mb: 1 }}> | ||||||
|                 </Box> |             <Button onClick={() => setConfirmOpen(false)}>Cancel</Button> | ||||||
|             </Box> |             <Button color="error" variant="contained" onClick={confirmDelete}>Delete</Button> | ||||||
|         </SectionContainer> |           </Box> | ||||||
|     ); |         </DialogContent> | ||||||
|  |       </Dialog> | ||||||
|  |     </Box> | ||||||
|  |   ); | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user