Compare commits
	
		
			2 Commits
		
	
	
		
			38626a3a81
			...
			e55d9a8cf4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | e55d9a8cf4 | ||
|   | b79d976c3e | 
							
								
								
									
										23
									
								
								src/App.jsx
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								src/App.jsx
									
									
									
									
									
								
							| @@ -1,3 +1,4 @@ | |||||||
|  | // App.jsx | ||||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||||
| import { Box, useMediaQuery } from '@mui/material'; | import { Box, useMediaQuery } from '@mui/material'; | ||||||
| import { useTheme } from '@mui/material/styles'; | import { useTheme } from '@mui/material/styles'; | ||||||
| @@ -6,9 +7,9 @@ import MenuDrawerPrivate, { OPEN_WIDTH, MINI_WIDTH } from './components/MenuDraw | |||||||
| import Footer from './components/Footer'; | 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 LoginPage from './private/LoginPage'; | import LoginPage from './private/LoginPage'; | ||||||
| import { BrowserRouter, 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'; | ||||||
|  |  | ||||||
| const DRAWER_EXPANDED = OPEN_WIDTH; | const DRAWER_EXPANDED = OPEN_WIDTH; | ||||||
| @@ -23,8 +24,7 @@ function PrivateRoute({ children }) { | |||||||
| export default function App() { | export default function App() { | ||||||
|   const theme = useTheme(); |   const theme = useTheme(); | ||||||
|   const isMobile = useMediaQuery('(max-width:900px)'); |   const isMobile = useMediaQuery('(max-width:900px)'); | ||||||
|   const [zone, setZone] = useState('public'); // public | restricted | private |   const [zone] = useState('public'); | ||||||
|  |  | ||||||
|   const [drawerExpanded, setDrawerExpanded] = useState(true); |   const [drawerExpanded, setDrawerExpanded] = useState(true); | ||||||
|   const [currentView, setCurrentView] = useState('Dashboard'); |   const [currentView, setCurrentView] = useState('Dashboard'); | ||||||
|  |  | ||||||
| @@ -34,13 +34,14 @@ export default function App() { | |||||||
|     <> |     <> | ||||||
|       <AppHeader |       <AppHeader | ||||||
|         zone="private" |         zone="private" | ||||||
|         onSelectMenuItem={setCurrentView} |         currentPage={currentView}              // <-- show this in the header | ||||||
|         drawerExpanded={drawerExpanded} |         leftOffset={mainLeft}                 // <-- keep title clear of the drawer | ||||||
|       /> |       /> | ||||||
|  |  | ||||||
|       <MenuDrawerPrivate |       <MenuDrawerPrivate | ||||||
|         onSelect={(value) => { |         onSelect={(value) => { | ||||||
|           setCurrentView(value === '/users/UserManagement' ? 'UserManagement' : value); |           // normalize any custom route keys | ||||||
|  |           setCurrentView(value); | ||||||
|         }} |         }} | ||||||
|         onExpandedChange={(expanded) => setDrawerExpanded(expanded)} |         onExpandedChange={(expanded) => setDrawerExpanded(expanded)} | ||||||
|       /> |       /> | ||||||
| @@ -64,12 +65,9 @@ export default function App() { | |||||||
|             path="/" |             path="/" | ||||||
|             element={ |             element={ | ||||||
|               <PrivateRoute> |               <PrivateRoute> | ||||||
|                 {zone === 'private' && <Clients />} |  | ||||||
|                 {zone === 'restricted' && <Clients />} |  | ||||||
|  |  | ||||||
|                 {zone === 'public' && currentView === 'Dashboard' && <Dashboard />} |                 {zone === 'public' && currentView === 'Dashboard' && <Dashboard />} | ||||||
|  |                 {zone === 'public' && currentView === '/Users/UserManagement' && <UserManagement />} | ||||||
|                 {zone === 'public' && currentView === 'UserManagement' && <UserManagement />} |                 {zone === 'public' && currentView === '/ProductsManagement/CatalogManagement/ProductCollections' && <FurnitureVariantManagement />} | ||||||
|               </PrivateRoute> |               </PrivateRoute> | ||||||
|             } |             } | ||||||
|           /> |           /> | ||||||
| @@ -78,7 +76,6 @@ export default function App() { | |||||||
|  |  | ||||||
|       <Box sx={{ height: 64 }} /> |       <Box sx={{ height: 64 }} /> | ||||||
|       <Footer zone={zone} /> |       <Footer zone={zone} /> | ||||||
|  |  | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
							
								
								
									
										55
									
								
								src/api/furnitureVariantApi.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/api/furnitureVariantApi.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | export default class FurnitureVariantApi { | ||||||
|  |   constructor(token) { | ||||||
|  |     this.baseUrl = 'https://inventory-bff.dream-views.com/api/v1/FurnitureVariant'; | ||||||
|  |     this.token = token;  | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   headers(json = true) { | ||||||
|  |     return { | ||||||
|  |       accept: 'application/json', | ||||||
|  |       ...(json ? { 'Content-Type': 'application/json' } : {}), | ||||||
|  |       ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async getAllVariants() { | ||||||
|  |     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(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Assuming similar endpoints; adjust names if backend differs. | ||||||
|  |   async createVariant(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 updateVariant(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 deleteVariant(payload) { | ||||||
|  |     // If your API is soft-delete via Update status, reuse updateVariant. | ||||||
|  |     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(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -154,7 +154,9 @@ export default function MenuDrawerPrivate({ | |||||||
|                                 handleToggleNode(key); |                                 handleToggleNode(key); | ||||||
|                             } else { |                             } else { | ||||||
|                                 if (node.title === 'Users Management') { |                                 if (node.title === 'Users Management') { | ||||||
|                                     onSelect?.('UserManagement'); |                                     onSelect?.('/Users/UserManagement'); | ||||||
|  |                                 } else if (node.title === 'Product Collections') { | ||||||
|  |                                     onSelect?.('/ProductsManagement/CatalogManagement/ProductCollections'); | ||||||
|                                 } else { |                                 } else { | ||||||
|                                     onSelect?.(node.title); |                                     onSelect?.(node.title); | ||||||
|                                 } |                                 } | ||||||
|   | |||||||
							
								
								
									
										181
									
								
								src/private/fornitures/AddOrEditFurnitureVariantForm.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/private/fornitures/AddOrEditFurnitureVariantForm.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | |||||||
|  | // src/private/furniture/AddOrEditFurnitureVariantForm.jsx | ||||||
|  | import { useEffect, useMemo, useState } from 'react'; | ||||||
|  | import { Box, Button, TextField, MenuItem, Grid } from '@mui/material'; | ||||||
|  | import FurnitureVariantApi from '../../api/furnitureVariantApi'; | ||||||
|  | import { useAuth } from '../../context/AuthContext'; | ||||||
|  |  | ||||||
|  | export default function AddOrEditFurnitureVariantForm({ initialData, onAdd, onCancel }) { | ||||||
|  |   const { user } = useAuth(); | ||||||
|  |   const token = user?.thalosToken || localStorage.getItem('thalosToken'); | ||||||
|  |   const api = useMemo(() => (new FurnitureVariantApi(token)), [token]); | ||||||
|  |  | ||||||
|  |   const [form, setForm] = useState({ | ||||||
|  |     _Id: '', | ||||||
|  |     modelId: '', | ||||||
|  |     name: '', | ||||||
|  |     color: '', | ||||||
|  |     line: '', | ||||||
|  |     stock: 0, | ||||||
|  |     price: 0, | ||||||
|  |     currency: 'USD', | ||||||
|  |     categoryId: '', | ||||||
|  |     providerId: '', | ||||||
|  |     attributes: { material: '', legs: '', origin: '' }, | ||||||
|  |     status: 'Active', | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (initialData) { | ||||||
|  |       setForm({ | ||||||
|  |         _Id: initialData._id || initialData._Id || '', | ||||||
|  |         modelId: initialData.modelId ?? '', | ||||||
|  |         name: initialData.name ?? '', | ||||||
|  |         color: initialData.color ?? '', | ||||||
|  |         line: initialData.line ?? '', | ||||||
|  |         stock: initialData.stock ?? 0, | ||||||
|  |         price: initialData.price ?? 0, | ||||||
|  |         currency: initialData.currency ?? 'USD', | ||||||
|  |         categoryId: initialData.categoryId ?? '', | ||||||
|  |         providerId: initialData.providerId ?? '', | ||||||
|  |         attributes: { | ||||||
|  |           material: initialData?.attributes?.material ?? '', | ||||||
|  |           legs: initialData?.attributes?.legs ?? '', | ||||||
|  |           origin: initialData?.attributes?.origin ?? '', | ||||||
|  |         }, | ||||||
|  |         status: initialData.status ?? 'Active', | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       setForm({ | ||||||
|  |         _Id: '', | ||||||
|  |         modelId: '', | ||||||
|  |         name: '', | ||||||
|  |         color: '', | ||||||
|  |         line: '', | ||||||
|  |         stock: 0, | ||||||
|  |         price: 0, | ||||||
|  |         currency: 'USD', | ||||||
|  |         categoryId: '', | ||||||
|  |         providerId: '', | ||||||
|  |         attributes: { material: '', legs: '', origin: '' }, | ||||||
|  |         status: 'Active', | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }, [initialData]); | ||||||
|  |  | ||||||
|  |   const setVal = (name, value) => setForm((p) => ({ ...p, [name]: value })); | ||||||
|  |   const setAttr = (name, value) => setForm((p) => ({ ...p, attributes: { ...p.attributes, [name]: value } })); | ||||||
|  |  | ||||||
|  |   const handleSubmit = async () => { | ||||||
|  |     try { | ||||||
|  |       if (form._Id) { | ||||||
|  |         // UPDATE | ||||||
|  |         const payload = { | ||||||
|  |           _Id: form._Id, | ||||||
|  |           modelId: form.modelId, | ||||||
|  |           name: form.name, | ||||||
|  |           color: form.color, | ||||||
|  |           line: form.line, | ||||||
|  |           stock: Number(form.stock) || 0, | ||||||
|  |           price: Number(form.price) || 0, | ||||||
|  |           currency: form.currency, | ||||||
|  |           categoryId: form.categoryId, | ||||||
|  |           providerId: form.providerId, | ||||||
|  |           attributes: { | ||||||
|  |             material: form.attributes.material, | ||||||
|  |             legs: form.attributes.legs, | ||||||
|  |             origin: form.attributes.origin, | ||||||
|  |           }, | ||||||
|  |           status: form.status, | ||||||
|  |         }; | ||||||
|  |         await api.updateVariant(payload); | ||||||
|  |       } else { | ||||||
|  |         // CREATE | ||||||
|  |         const payload = { | ||||||
|  |           modelId: form.modelId, | ||||||
|  |           name: form.name, | ||||||
|  |           color: form.color, | ||||||
|  |           line: form.line, | ||||||
|  |           stock: Number(form.stock) || 0, | ||||||
|  |           price: Number(form.price) || 0, | ||||||
|  |           currency: form.currency, | ||||||
|  |           categoryId: form.categoryId, | ||||||
|  |           providerId: form.providerId, | ||||||
|  |           attributes: { | ||||||
|  |             material: form.attributes.material, | ||||||
|  |             legs: form.attributes.legs, | ||||||
|  |             origin: form.attributes.origin, | ||||||
|  |           }, | ||||||
|  |           status: form.status, | ||||||
|  |         }; | ||||||
|  |         await api.createVariant(payload); | ||||||
|  |       } | ||||||
|  |       onAdd?.(); | ||||||
|  |     } catch (err) { | ||||||
|  |       console.error('Submit variant failed:', err); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <Box sx={{ py: 2 }}> | ||||||
|  |       <Grid container spacing={2}> | ||||||
|  |         <Grid item xs={12} md={6}> | ||||||
|  |           <TextField fullWidth label="Model Id" value={form.modelId} onChange={(e) => setVal('modelId', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |         <Grid item xs={12} md={6}> | ||||||
|  |           <TextField fullWidth label="Name" value={form.name} onChange={(e) => setVal('name', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |  | ||||||
|  |         <Grid item xs={12} md={4}> | ||||||
|  |           <TextField fullWidth label="Color" value={form.color} onChange={(e) => setVal('color', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |         <Grid item xs={12} md={4}> | ||||||
|  |           <TextField fullWidth label="Line" value={form.line} onChange={(e) => setVal('line', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |         <Grid item xs={12} md={2}> | ||||||
|  |           <TextField fullWidth type="number" label="Stock" value={form.stock} onChange={(e) => setVal('stock', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |         <Grid item xs={12} md={2}> | ||||||
|  |           <TextField fullWidth type="number" label="Price" value={form.price} onChange={(e) => setVal('price', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |  | ||||||
|  |         <Grid item xs={12} md={3}> | ||||||
|  |           <TextField fullWidth label="Currency" value={form.currency} onChange={(e) => setVal('currency', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |         <Grid item xs={12} md={4}> | ||||||
|  |           <TextField fullWidth label="Category Id" value={form.categoryId} onChange={(e) => setVal('categoryId', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |         <Grid item xs={12} md={5}> | ||||||
|  |           <TextField fullWidth label="Provider Id" value={form.providerId} onChange={(e) => setVal('providerId', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |  | ||||||
|  |         <Grid item xs={12} md={4}> | ||||||
|  |           <TextField fullWidth label="Material" value={form.attributes.material} onChange={(e) => setAttr('material', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |         <Grid item xs={12} md={4}> | ||||||
|  |           <TextField fullWidth label="Legs" value={form.attributes.legs} onChange={(e) => setAttr('legs', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |         <Grid item xs={12} md={4}> | ||||||
|  |           <TextField fullWidth label="Origin" value={form.attributes.origin} onChange={(e) => setAttr('origin', e.target.value)} /> | ||||||
|  |         </Grid> | ||||||
|  |  | ||||||
|  |         <Grid item xs={12} md={4}> | ||||||
|  |           <TextField | ||||||
|  |             fullWidth | ||||||
|  |             select | ||||||
|  |             label="Status" | ||||||
|  |             value={form.status} | ||||||
|  |             onChange={(e) => setVal('status', e.target.value)} | ||||||
|  |           > | ||||||
|  |             <MenuItem value="Active">Active</MenuItem> | ||||||
|  |             <MenuItem value="Inactive">Inactive</MenuItem> | ||||||
|  |           </TextField> | ||||||
|  |         </Grid> | ||||||
|  |       </Grid> | ||||||
|  |  | ||||||
|  |       <Box display="flex" justifyContent="flex-end" mt={3} gap={1}> | ||||||
|  |         <Button onClick={onCancel} className="button-transparent">Cancel</Button> | ||||||
|  |         <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> | ||||||
|  |       </Box> | ||||||
|  |     </Box> | ||||||
|  |   ); | ||||||
|  | } | ||||||
							
								
								
									
										254
									
								
								src/private/fornitures/FurnitureVariantManagement.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								src/private/fornitures/FurnitureVariantManagement.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,254 @@ | |||||||
|  | import SectionContainer from '../../components/SectionContainer'; | ||||||
|  | import { useEffect, useRef, useState } from 'react'; | ||||||
|  | import { DataGrid } from '@mui/x-data-grid'; | ||||||
|  | import { | ||||||
|  |   Typography, Button, Dialog, DialogTitle, DialogContent, | ||||||
|  |   IconButton, Box | ||||||
|  | } from '@mui/material'; | ||||||
|  | import EditRoundedIcon from '@mui/icons-material/EditRounded'; | ||||||
|  | import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | ||||||
|  | import AddOrEditFurnitureVariantForm from './AddOrEditFurnitureVariantForm'; | ||||||
|  | import FurnitureVariantApi from '../../api/furnitureVariantApi'; | ||||||
|  | import { useAuth } from '../../context/AuthContext'; | ||||||
|  | import useApiToast from '../../hooks/useApiToast'; | ||||||
|  |  | ||||||
|  | const columnsBase = [ | ||||||
|  |   { field: 'modelId', headerName: 'Model Id', width: 260 }, | ||||||
|  |   { field: 'name', headerName: 'Name', width: 220 }, | ||||||
|  |   { field: 'color', headerName: 'Color', width: 160 }, | ||||||
|  |   { field: 'line', headerName: 'Line', width: 160 }, | ||||||
|  |   { field: 'stock', headerName: 'Stock', width: 100, type: 'number' }, | ||||||
|  |   { field: 'price', headerName: 'Price', width: 120, type: 'number', | ||||||
|  |     valueFormatter: (p) => p?.value != null ? Number(p.value).toFixed(2) : '—' | ||||||
|  |   }, | ||||||
|  |   { field: 'currency', headerName: 'Currency', width: 120 }, | ||||||
|  |   { field: 'categoryId', headerName: 'Category Id', width: 280 }, | ||||||
|  |   { field: 'providerId', headerName: 'Provider Id', width: 280 }, | ||||||
|  |   { | ||||||
|  |     field: 'attributes.material', | ||||||
|  |     headerName: 'Material', | ||||||
|  |     width: 160, | ||||||
|  |     valueGetter: (p) => p?.row?.attributes?.material ?? '—' | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     field: 'attributes.legs', | ||||||
|  |     headerName: 'Legs', | ||||||
|  |     width: 160, | ||||||
|  |     valueGetter: (p) => p?.row?.attributes?.legs ?? '—' | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     field: 'attributes.origin', | ||||||
|  |     headerName: 'Origin', | ||||||
|  |     width: 160, | ||||||
|  |     valueGetter: (p) => p?.row?.attributes?.origin ?? '—' | ||||||
|  |   }, | ||||||
|  |   { field: 'status', headerName: 'Status', width: 120 }, | ||||||
|  |   { | ||||||
|  |     field: 'createdAt', | ||||||
|  |     headerName: 'Created At', | ||||||
|  |     width: 180, | ||||||
|  |     valueFormatter: (p) => p?.value ? new Date(p.value).toLocaleString() : '—' | ||||||
|  |   }, | ||||||
|  |   { field: 'createdBy', headerName: 'Created By', width: 160, valueGetter: (p) => p?.row?.createdBy ?? '—' }, | ||||||
|  |   { | ||||||
|  |     field: 'updatedAt', | ||||||
|  |     headerName: 'Updated At', | ||||||
|  |     width: 180, | ||||||
|  |     valueFormatter: (p) => p?.value ? new Date(p.value).toLocaleString() : '—' | ||||||
|  |   }, | ||||||
|  |   { field: 'updatedBy', headerName: 'Updated By', width: 160, valueGetter: (p) => p?.row?.updatedBy ?? '—' }, | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | export default function FurnitureVariantManagement() { | ||||||
|  |   const { user } = useAuth(); | ||||||
|  |   const token = user?.thalosToken || localStorage.getItem('thalosToken'); | ||||||
|  |   const apiRef = useRef(null); | ||||||
|  |  | ||||||
|  |   const [rows, setRows] = useState([]); | ||||||
|  |   const [open, setOpen] = useState(false); | ||||||
|  |   const [editingData, setEditingData] = useState(null); | ||||||
|  |   const [confirmOpen, setConfirmOpen] = useState(false); | ||||||
|  |   const [rowToDelete, setRowToDelete] = useState(null); | ||||||
|  |   const { handleError } = useApiToast(); | ||||||
|  |   const hasLoaded = useRef(false); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     apiRef.current = new FurnitureVariantApi(token); | ||||||
|  |   }, [token]); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (!hasLoaded.current) { | ||||||
|  |       loadData(); | ||||||
|  |       hasLoaded.current = true; | ||||||
|  |     } | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   const loadData = async () => { | ||||||
|  |     try { | ||||||
|  |       const data = await apiRef.current.getAllVariants(); | ||||||
|  |       setRows(Array.isArray(data) ? data : []); | ||||||
|  |     } catch (err) { | ||||||
|  |       console.error('Error loading variants:', err); | ||||||
|  |       handleError(err, 'Failed to load furniture variants'); | ||||||
|  |       setRows([]); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleEditClick = (params) => { | ||||||
|  |     if (!params?.row) return; | ||||||
|  |     const r = params.row; | ||||||
|  |     const normalized = { | ||||||
|  |       _id: r._id || r._Id || '', | ||||||
|  |       id: r.id || r.Id || '', | ||||||
|  |       modelId: r.modelId ?? '', | ||||||
|  |       name: r.name ?? '', | ||||||
|  |       color: r.color ?? '', | ||||||
|  |       line: r.line ?? '', | ||||||
|  |       stock: r.stock ?? 0, | ||||||
|  |       price: r.price ?? 0, | ||||||
|  |       currency: r.currency ?? 'USD', | ||||||
|  |       categoryId: r.categoryId ?? '', | ||||||
|  |       providerId: r.providerId ?? '', | ||||||
|  |       attributes: { | ||||||
|  |         material: r?.attributes?.material ?? '', | ||||||
|  |         legs: r?.attributes?.legs ?? '', | ||||||
|  |         origin: r?.attributes?.origin ?? '', | ||||||
|  |       }, | ||||||
|  |       status: r.status ?? 'Active', | ||||||
|  |     }; | ||||||
|  |     setEditingData(normalized); | ||||||
|  |     setOpen(true); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleDeleteClick = (row) => { | ||||||
|  |     setRowToDelete(row); | ||||||
|  |     setConfirmOpen(true); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleConfirmDelete = async () => { | ||||||
|  |     try { | ||||||
|  |       if (!apiRef.current || !rowToDelete?._id) throw new Error('Missing API or id'); | ||||||
|  |       // If your inventory BFF uses soft delete via Update (status=Inactive), do this: | ||||||
|  |       const payload = { | ||||||
|  |         _Id: rowToDelete._id || rowToDelete._Id, | ||||||
|  |         modelId: rowToDelete.modelId, | ||||||
|  |         name: rowToDelete.name, | ||||||
|  |         color: rowToDelete.color, | ||||||
|  |         line: rowToDelete.line, | ||||||
|  |         stock: rowToDelete.stock, | ||||||
|  |         price: rowToDelete.price, | ||||||
|  |         currency: rowToDelete.currency, | ||||||
|  |         categoryId: rowToDelete.categoryId, | ||||||
|  |         providerId: rowToDelete.providerId, | ||||||
|  |         attributes: { | ||||||
|  |           material: rowToDelete?.attributes?.material ?? '', | ||||||
|  |           legs: rowToDelete?.attributes?.legs ?? '', | ||||||
|  |           origin: rowToDelete?.attributes?.origin ?? '', | ||||||
|  |         }, | ||||||
|  |         status: 'Inactive', | ||||||
|  |       }; | ||||||
|  |       // Prefer update soft-delete; if you truly have DELETE, switch to apiRef.current.deleteVariant({ _Id: ... }) | ||||||
|  |       await apiRef.current.updateVariant(payload); | ||||||
|  |       await loadData(); | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error('Delete failed:', e); | ||||||
|  |     } finally { | ||||||
|  |       setConfirmOpen(false); | ||||||
|  |       setRowToDelete(null); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const columns = [ | ||||||
|  |     { | ||||||
|  |       field: 'actions', | ||||||
|  |       headerName: '', | ||||||
|  |       width: 130, | ||||||
|  |       renderCell: (params) => ( | ||||||
|  |         <Box display="flex" alignItems="center" justifyContent="flex-start" height="100%" gap={1}> | ||||||
|  |           <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> | ||||||
|  |       ) | ||||||
|  |     }, | ||||||
|  |     ...columnsBase, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <SectionContainer sx={{ width: '100%' }}> | ||||||
|  |       <Dialog open={open} onClose={() => { setOpen(false); setEditingData(null); }} maxWidth="md" fullWidth> | ||||||
|  |         <DialogTitle>{editingData ? 'Edit Furniture Variant' : 'Add Furniture Variant'}</DialogTitle> | ||||||
|  |         <DialogContent> | ||||||
|  |           <AddOrEditFurnitureVariantForm | ||||||
|  |             initialData={editingData} | ||||||
|  |             onCancel={() => { setOpen(false); setEditingData(null); }} | ||||||
|  |             onAdd={async () => { | ||||||
|  |               await loadData(); | ||||||
|  |               setOpen(false); | ||||||
|  |               setEditingData(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={handleConfirmDelete} className="button-gold">Delete</Button> | ||||||
|  |           </Box> | ||||||
|  |         </DialogContent> | ||||||
|  |       </Dialog> | ||||||
|  |  | ||||||
|  |       <Box mt={2} sx={{ width: '100%', overflowX: 'auto' }}> | ||||||
|  |         <DataGrid | ||||||
|  |           rows={rows} | ||||||
|  |           columns={columns} | ||||||
|  |           pageSize={5} | ||||||
|  |           rowsPerPageOptions={[5]} | ||||||
|  |           getRowSpacing={() => ({ top: 4, bottom: 4 })} | ||||||
|  |           getRowId={(row) => row._id || row.id || row.modelId} | ||||||
|  |           autoHeight | ||||||
|  |           disableColumnMenu | ||||||
|  |           getRowHeight={() => 'auto'} | ||||||
|  |           sx={{ | ||||||
|  |             '& .MuiDataGrid-cell': { display: 'flex', alignItems: 'center' }, | ||||||
|  |             '& .MuiDataGrid-columnHeader': { display: 'flex', alignItems: 'center' }, | ||||||
|  |           }} | ||||||
|  |         /> | ||||||
|  |         <Box display="flex" justifyContent="flex-end" mt={2}> | ||||||
|  |           <Button variant="contained" className="button-gold" onClick={() => setOpen(true)}> | ||||||
|  |             Add Variant | ||||||
|  |           </Button> | ||||||
|  |         </Box> | ||||||
|  |       </Box> | ||||||
|  |     </SectionContainer> | ||||||
|  |   ); | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user