Compare commits
	
		
			10 Commits
		
	
	
		
			9cdb76273d
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f7adaf1b18 | ||
|   | b3209a4019 | ||
|   | efdb48919f | ||
|   | 6b8d5acc0d | ||
|   | 01a19b9144 | ||
|   | 74d6a8b269 | ||
|   | c33de6ada5 | ||
|   | 15107a48bd | ||
|   | 73699009fc | ||
|   | 55dc96085d | 
| @@ -1,7 +1,7 @@ | |||||||
| export default class ProductsApi { | export default class ProductsApi { | ||||||
|   constructor(token) { |   constructor(token) { | ||||||
|     this.baseUrl = 'https://inventory-bff.dream-views.com/api/v1/FurnitureVariant'; |     this.baseUrl = 'https://inventory-bff.dream-views.com/api/v1/FurnitureVariant'; | ||||||
|     this.token = token;  |     this.token = token; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   headers(json = true) { |   headers(json = true) { | ||||||
| @@ -52,4 +52,15 @@ export default class ProductsApi { | |||||||
|     if (!res.ok) throw new Error(`Delete error ${res.status}: ${await res.text()}`); |     if (!res.ok) throw new Error(`Delete error ${res.status}: ${await res.text()}`); | ||||||
|     return res.json(); |     return res.json(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   async changeStatusVariant(payload) { | ||||||
|  |     // If your API is change status, reuse updateVariant. | ||||||
|  |     const res = await fetch(`${this.baseUrl}/ChangeStatus`, { | ||||||
|  |       method: 'PATCH', | ||||||
|  |       headers: this.headers(), | ||||||
|  |       body: JSON.stringify(payload), | ||||||
|  |     }); | ||||||
|  |     if (!res.ok) throw new Error(`ChangeStatus error ${res.status}: ${await res.text()}`); | ||||||
|  |     return res.json(); | ||||||
|  |   } | ||||||
| } | } | ||||||
| @@ -43,7 +43,7 @@ function formatDateSafe(value) { | |||||||
|   }).format(d); |   }).format(d); | ||||||
| } | } | ||||||
|  |  | ||||||
| export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel, materials: materialsProp = [], initialMaterialNames = [] }) { | export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel, materials: materialsProp = [], initialMaterialNames = [], viewOnly = false }) { | ||||||
|   const { user } = useAuth(); |   const { user } = useAuth(); | ||||||
|   const token = user?.thalosToken || localStorage.getItem('thalosToken'); |   const token = user?.thalosToken || localStorage.getItem('thalosToken'); | ||||||
|   const api = useMemo(() => new CategoriesApi(token), [token]); |   const api = useMemo(() => new CategoriesApi(token), [token]); | ||||||
| @@ -52,14 +52,14 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel, ma | |||||||
|   const [types, setTypes] = useState([]); |   const [types, setTypes] = useState([]); | ||||||
|   const [allTags, setAllTags] = useState([]); |   const [allTags, setAllTags] = useState([]); | ||||||
|  |  | ||||||
| const tagLabelById = useMemo(() => { |   const tagLabelById = useMemo(() => { | ||||||
|   const map = {}; |     const map = {}; | ||||||
|   for (const t of allTags) { |     for (const t of allTags) { | ||||||
|     const key = t._id; |       const key = t._id; | ||||||
|     map[key] = t.tagName || t.name || key; |       map[key] = t.tagName || t.name || key; | ||||||
|   } |     } | ||||||
|   return map; |     return map; | ||||||
| }, [allTags]); |   }, [allTags]); | ||||||
|  |  | ||||||
|   const [form, setForm] = useState({ |   const [form, setForm] = useState({ | ||||||
|     _id: '', |     _id: '', | ||||||
| @@ -248,6 +248,7 @@ const tagLabelById = useMemo(() => { | |||||||
|           fullWidth |           fullWidth | ||||||
|           sx={{ mb: 2 }} |           sx={{ mb: 2 }} | ||||||
|           InputProps={{ readOnly: true }} |           InputProps={{ readOnly: true }} | ||||||
|  |           disabled={viewOnly} | ||||||
|         /> |         /> | ||||||
|       )} |       )} | ||||||
|  |  | ||||||
| @@ -259,6 +260,7 @@ const tagLabelById = useMemo(() => { | |||||||
|         fullWidth |         fullWidth | ||||||
|         sx={{ mb: 2 }} |         sx={{ mb: 2 }} | ||||||
|         required |         required | ||||||
|  |         disabled={viewOnly} | ||||||
|       /> |       /> | ||||||
|  |  | ||||||
|       <TextField |       <TextField | ||||||
| @@ -270,6 +272,7 @@ const tagLabelById = useMemo(() => { | |||||||
|         fullWidth |         fullWidth | ||||||
|         sx={{ mb: 2 }} |         sx={{ mb: 2 }} | ||||||
|         required |         required | ||||||
|  |         disabled={viewOnly} | ||||||
|       > |       > | ||||||
|         {types.map((t) => { |         {types.map((t) => { | ||||||
|           const value = t._id; |           const value = t._id; | ||||||
| @@ -304,6 +307,7 @@ const tagLabelById = useMemo(() => { | |||||||
|         }} |         }} | ||||||
|         fullWidth |         fullWidth | ||||||
|         sx={{ mb: 2 }} |         sx={{ mb: 2 }} | ||||||
|  |         disabled={viewOnly} | ||||||
|       > |       > | ||||||
|         {allTags.map((t) => { |         {allTags.map((t) => { | ||||||
|           const value = t._id; |           const value = t._id; | ||||||
| @@ -323,6 +327,7 @@ const tagLabelById = useMemo(() => { | |||||||
|         onChange={handleChange} |         onChange={handleChange} | ||||||
|         fullWidth |         fullWidth | ||||||
|         sx={{ mb: 2 }} |         sx={{ mb: 2 }} | ||||||
|  |         disabled={viewOnly} | ||||||
|       /> |       /> | ||||||
|  |  | ||||||
|       <TextField |       <TextField | ||||||
| @@ -333,6 +338,7 @@ const tagLabelById = useMemo(() => { | |||||||
|         onChange={handleChange} |         onChange={handleChange} | ||||||
|         fullWidth |         fullWidth | ||||||
|         sx={{ mb: 2 }} |         sx={{ mb: 2 }} | ||||||
|  |         disabled={viewOnly} | ||||||
|       /> |       /> | ||||||
|  |  | ||||||
|       <TextField |       <TextField | ||||||
| @@ -343,6 +349,7 @@ const tagLabelById = useMemo(() => { | |||||||
|         fullWidth |         fullWidth | ||||||
|         sx={{ mb: 2 }} |         sx={{ mb: 2 }} | ||||||
|         required |         required | ||||||
|  |         disabled={viewOnly} | ||||||
|       /> |       /> | ||||||
|  |  | ||||||
|       <TextField |       <TextField | ||||||
| @@ -353,6 +360,7 @@ const tagLabelById = useMemo(() => { | |||||||
|         select |         select | ||||||
|         fullWidth |         fullWidth | ||||||
|         sx={{ mb: 2 }} |         sx={{ mb: 2 }} | ||||||
|  |         disabled={viewOnly} | ||||||
|       > |       > | ||||||
|         <MenuItem value="Active">Active</MenuItem> |         <MenuItem value="Active">Active</MenuItem> | ||||||
|         <MenuItem value="Inactive">Inactive</MenuItem> |         <MenuItem value="Inactive">Inactive</MenuItem> | ||||||
| @@ -368,12 +376,14 @@ const tagLabelById = useMemo(() => { | |||||||
|       ) : null} |       ) : null} | ||||||
|  |  | ||||||
|       <Box display="flex" justifyContent="space-between" gap={1} mt={3}> |       <Box display="flex" justifyContent="space-between" gap={1} mt={3}> | ||||||
|         {form._id ? ( |         {form._id && !viewOnly ? ( | ||||||
|           <Button color="error" onClick={handleDelete}>Delete</Button> |           <Button color="error" onClick={handleDelete}>Delete</Button> | ||||||
|         ) : <span />} |         ) : <span />} | ||||||
|         <Box sx={{ display: 'flex', gap: 1 }}> |         <Box sx={{ display: 'flex', gap: 1 }}> | ||||||
|           <Button onClick={onCancel} className="button-transparent">Cancel</Button> |           <Button onClick={onCancel} className="button-transparent">{viewOnly ? 'Close' : 'Cancel'}</Button> | ||||||
|           <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> |           {!viewOnly && ( | ||||||
|  |             <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> | ||||||
|  |           )} | ||||||
|         </Box> |         </Box> | ||||||
|       </Box> |       </Box> | ||||||
|     </Paper> |     </Paper> | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import { | |||||||
| import { DataGrid } from '@mui/x-data-grid'; | import { DataGrid } from '@mui/x-data-grid'; | ||||||
| import EditRoundedIcon from '@mui/icons-material/EditRounded'; | import EditRoundedIcon from '@mui/icons-material/EditRounded'; | ||||||
| import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | ||||||
|  | import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded'; | ||||||
| import AddOrEditCategoryForm from './AddOrEditCategoryForm'; | import AddOrEditCategoryForm from './AddOrEditCategoryForm'; | ||||||
| import CategoriesApi from '../../../api/CategoriesApi'; | import CategoriesApi from '../../../api/CategoriesApi'; | ||||||
| import { useAuth } from '../../../context/AuthContext'; | import { useAuth } from '../../../context/AuthContext'; | ||||||
| @@ -22,6 +23,7 @@ export default function Categories() { | |||||||
|   const [editingCategory, setEditingCategory] = useState(null); |   const [editingCategory, setEditingCategory] = useState(null); | ||||||
|   const [confirmOpen, setConfirmOpen] = useState(false); |   const [confirmOpen, setConfirmOpen] = useState(false); | ||||||
|   const [rowToDelete, setRowToDelete] = useState(null); |   const [rowToDelete, setRowToDelete] = useState(null); | ||||||
|  |   const [viewOnly, setViewOnly] = useState(false); | ||||||
|   const hasLoaded = useRef(false); |   const hasLoaded = useRef(false); | ||||||
|  |  | ||||||
|   const pageSize = 100; // Número de filas por página |   const pageSize = 100; // Número de filas por página | ||||||
| @@ -68,11 +70,13 @@ export default function Categories() { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleAddClick = () => { |   const handleAddClick = () => { | ||||||
|  |     setViewOnly(false); | ||||||
|     setEditingCategory(null); |     setEditingCategory(null); | ||||||
|     setOpen(true); |     setOpen(true); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleEditClick = (params) => { |   const handleEditClick = (params) => { | ||||||
|  |     setViewOnly(false); | ||||||
|     const r = params?.row; |     const r = params?.row; | ||||||
|     if (!r) return; |     if (!r) return; | ||||||
|     setEditingCategory({ |     setEditingCategory({ | ||||||
| @@ -145,7 +149,7 @@ export default function Categories() { | |||||||
|     { |     { | ||||||
|       field: 'actions', |       field: 'actions', | ||||||
|       headerName: '', |       headerName: '', | ||||||
|       width: 130, |       width: 150, | ||||||
|       sortable: false, |       sortable: false, | ||||||
|       filterable: false, |       filterable: false, | ||||||
|       disableExport: true, |       disableExport: true, | ||||||
| @@ -164,6 +168,44 @@ export default function Categories() { | |||||||
|           > |           > | ||||||
|             <EditRoundedIcon fontSize="small" /> |             <EditRoundedIcon fontSize="small" /> | ||||||
|           </IconButton> |           </IconButton> | ||||||
|  |           <IconButton | ||||||
|  |             size="small" | ||||||
|  |             sx={{ | ||||||
|  |               backgroundColor: '#E3F2FD', | ||||||
|  |               color: '#1565C0', | ||||||
|  |               '&:hover': { backgroundColor: '#BBDEFB' }, | ||||||
|  |               borderRadius: 2, | ||||||
|  |               p: 1, | ||||||
|  |             }} | ||||||
|  |             onClick={() => { | ||||||
|  |               const r = params?.row; | ||||||
|  |               if (!r) return; | ||||||
|  |               setEditingCategory({ | ||||||
|  |                 _id: String(r._id || ''), | ||||||
|  |                 id: String(r.id || ''), | ||||||
|  |                 tagName: r.tagName || r.name || '', | ||||||
|  |                 typeId: r.typeId || '', | ||||||
|  |                 parentTagId: Array.isArray(r.parentTagId) ? r.parentTagId : [], | ||||||
|  |                 slug: r.slug || '', | ||||||
|  |                 displayOrder: Number(r.displayOrder ?? 0), | ||||||
|  |                 icon: r.icon || '', | ||||||
|  |                 status: r.status ?? 'Active', | ||||||
|  |                 materialNames: Array.isArray(r.materialNames) | ||||||
|  |                   ? r.materialNames | ||||||
|  |                   : (typeof r.material === 'string' | ||||||
|  |                     ? r.material.split(',').map(s => s.trim()).filter(Boolean) | ||||||
|  |                     : []), | ||||||
|  |                 createdAt: r.createdAt ?? null, | ||||||
|  |                 createdBy: r.createdBy ?? null, | ||||||
|  |                 updatedAt: r.updatedAt ?? null, | ||||||
|  |                 updatedBy: r.updatedBy ?? null, | ||||||
|  |               }); | ||||||
|  |               setViewOnly(true); | ||||||
|  |               setOpen(true); | ||||||
|  |             }} | ||||||
|  |           > | ||||||
|  |             <VisibilityRoundedIcon fontSize="small" /> | ||||||
|  |           </IconButton> | ||||||
|           <IconButton |           <IconButton | ||||||
|             size="small" |             size="small" | ||||||
|             sx={{ |             sx={{ | ||||||
| @@ -254,7 +296,7 @@ export default function Categories() { | |||||||
|       }} |       }} | ||||||
|     > |     > | ||||||
|       <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}> |       <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}> | ||||||
|         <Typography variant="h6">Categories</Typography> |         <Typography color='text.primary' variant="h6">Categories</Typography> | ||||||
|  |  | ||||||
|         <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}> |         <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}> | ||||||
|           <ToggleButtonGroup |           <ToggleButtonGroup | ||||||
| @@ -319,6 +361,7 @@ export default function Categories() { | |||||||
|             initialMaterialNames={editingCategory?.materialNames || []} |             initialMaterialNames={editingCategory?.materialNames || []} | ||||||
|             onAdd={handleFormDone} |             onAdd={handleFormDone} | ||||||
|             onCancel={() => { setOpen(false); setEditingCategory(null); }} |             onCancel={() => { setOpen(false); setEditingCategory(null); }} | ||||||
|  |             viewOnly={viewOnly} | ||||||
|           /> |           /> | ||||||
|         </DialogContent> |         </DialogContent> | ||||||
|       </Dialog> |       </Dialog> | ||||||
| @@ -327,8 +370,8 @@ export default function Categories() { | |||||||
|         <DialogTitle>Delete Category</DialogTitle> |         <DialogTitle>Delete Category</DialogTitle> | ||||||
|         <DialogContent> |         <DialogContent> | ||||||
|           <Box sx={{ display: 'flex', gap: 1, mt: 2, mb: 1 }}> |           <Box sx={{ display: 'flex', gap: 1, mt: 2, mb: 1 }}> | ||||||
|             <Button onClick={() => setConfirmOpen(false)}>Cancel</Button> |             <Button onClick={() => setConfirmOpen(false)} className='button-transparent'>Cancel</Button> | ||||||
|             <Button color="error" variant="contained" onClick={confirmDelete}>Delete</Button> |             <Button color="error" variant="contained" onClick={confirmDelete} className="button-gold">Delete</Button> | ||||||
|           </Box> |           </Box> | ||||||
|         </DialogContent> |         </DialogContent> | ||||||
|       </Dialog> |       </Dialog> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| // src/private/furniture/AddOrEditFurnitureVariantForm.jsx | // src/private/furniture/AddOrEditFurnitureVariantForm.jsx | ||||||
| import { useEffect, useMemo, useState } from 'react'; | import { useEffect, useMemo, useState } from 'react'; | ||||||
| import { Box, Button, TextField, MenuItem, Grid, CircularProgress } from '@mui/material'; | import { Box, Button, TextField, MenuItem, CircularProgress } from '@mui/material'; | ||||||
| import FurnitureVariantApi from '../../../api/ProductsApi'; | import FurnitureVariantApi from '../../../api/ProductsApi'; | ||||||
| import CategoriesApi from '../../../api/CategoriesApi'; | import CategoriesApi from '../../../api/CategoriesApi'; | ||||||
| import TagTypeApi from '../../../api/TagTypeApi'; | import TagTypeApi from '../../../api/TagTypeApi'; | ||||||
| @@ -19,7 +19,7 @@ const TYPE_NAMES = { | |||||||
|   origin: 'Origin', |   origin: 'Origin', | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default function AddOrEditProductCollectionForm({ initialData, onAdd, onCancel }) { | export default function AddOrEditProductCollectionForm({ initialData, onAdd, onCancel, viewOnly = false }) { | ||||||
|   const { user } = useAuth(); |   const { user } = useAuth(); | ||||||
|   const token = user?.thalosToken || localStorage.getItem('thalosToken'); |   const token = user?.thalosToken || localStorage.getItem('thalosToken'); | ||||||
|  |  | ||||||
| @@ -197,158 +197,164 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC | |||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Box> |     <Box> | ||||||
|       <Grid container spacing={2}> |       <Box display="flex" flexDirection="column" gap={2}> | ||||||
|         <Grid item xs={12} md={6}> |         {/* Name */} | ||||||
|           <TextField label="Model Id" fullWidth value={form.modelId} onChange={(e) => setVal('modelId', e.target.value)} /> |         <TextField | ||||||
|         </Grid> |           label="Name" | ||||||
|         <Grid item xs={12} md={6}> |           fullWidth | ||||||
|           <TextField label="Name" fullWidth value={form.name} onChange={(e) => setVal('name', e.target.value)} /> |           value={form.name} | ||||||
|         </Grid> |           onChange={(e) => setVal('name', e.target.value)} | ||||||
|  |           sx={{ mt: 1 }} | ||||||
|  |           disabled={viewOnly} | ||||||
|  |         /> | ||||||
|  |  | ||||||
|         {/* Clasificación */} |         {/* Category / Provider */} | ||||||
|         <Grid item xs={12} md={6}> |         <TextField | ||||||
|           <TextField |           select | ||||||
|             select |           label="Category" | ||||||
|             label="Category" |           fullWidth | ||||||
|             fullWidth |           value={form.categoryId} | ||||||
|             value={form.categoryId} |           onChange={(e) => setVal('categoryId', e.target.value)} | ||||||
|             onChange={(e) => setVal('categoryId', e.target.value)} |           helperText="Se envía el tagName por ahora" | ||||||
|             helperText="Se envía el tagName por ahora" |           disabled={viewOnly} | ||||||
|           > |         > | ||||||
|             {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} |           {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} | ||||||
|             {!loadingTags && options.categories.filter(t => t.status !== 'Inactive').map(tag => ( |           {!loadingTags && options.categories.filter(t => t.status !== 'Inactive').map(tag => ( | ||||||
|               <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> |             <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> | ||||||
|             ))} |           ))} | ||||||
|           </TextField> |         </TextField> | ||||||
|         </Grid> |         <TextField | ||||||
|  |           select | ||||||
|  |           label="Provider" | ||||||
|  |           fullWidth | ||||||
|  |           value={form.providerId} | ||||||
|  |           onChange={(e) => setVal('providerId', e.target.value)} | ||||||
|  |           disabled={viewOnly} | ||||||
|  |         > | ||||||
|  |           {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} | ||||||
|  |           {!loadingTags && options.providers.filter(t => t.status !== 'Inactive').map(tag => ( | ||||||
|  |             <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> | ||||||
|  |           ))} | ||||||
|  |         </TextField> | ||||||
|  |  | ||||||
|         <Grid item xs={12} md={6}> |         <Box display="flex" gap={2}> | ||||||
|           <TextField |           {/* Color */} | ||||||
|             select |           <Box flex={1}> | ||||||
|             label="Provider" |             <TextField | ||||||
|             fullWidth |               select | ||||||
|             value={form.providerId} |               label="Color" | ||||||
|             onChange={(e) => setVal('providerId', e.target.value)} |               fullWidth | ||||||
|           > |               value={form.color} | ||||||
|             {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} |               onChange={(e) => setVal('color', e.target.value)} | ||||||
|             {!loadingTags && options.providers.filter(t => t.status !== 'Inactive').map(tag => ( |               disabled={viewOnly} | ||||||
|               <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> |             > | ||||||
|             ))} |               {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} | ||||||
|           </TextField> |               {!loadingTags && options.colors.filter(t => t.status !== 'Inactive').map(tag => ( | ||||||
|         </Grid> |                 <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> | ||||||
|  |               ))} | ||||||
|  |             </TextField> | ||||||
|  |           </Box> | ||||||
|  |           {/* Line */} | ||||||
|  |           <Box flex={1}> | ||||||
|  |             <TextField | ||||||
|  |               select | ||||||
|  |               label="Line" | ||||||
|  |               fullWidth | ||||||
|  |               value={form.line} | ||||||
|  |               onChange={(e) => setVal('line', e.target.value)} | ||||||
|  |               disabled={viewOnly} | ||||||
|  |             > | ||||||
|  |               {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} | ||||||
|  |               {!loadingTags && options.lines.filter(t => t.status !== 'Inactive').map(tag => ( | ||||||
|  |                 <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> | ||||||
|  |               ))} | ||||||
|  |             </TextField> | ||||||
|  |           </Box> | ||||||
|  |           {/* Material */} | ||||||
|  |           <Box flex={1}> | ||||||
|  |             <TextField | ||||||
|  |               select | ||||||
|  |               label="Material" | ||||||
|  |               fullWidth | ||||||
|  |               value={form.attributes.material} | ||||||
|  |               onChange={(e) => setVal('attributes.material', e.target.value)} | ||||||
|  |               disabled={viewOnly} | ||||||
|  |             > | ||||||
|  |               {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} | ||||||
|  |               {!loadingTags && options.materials.filter(t => t.status !== 'Inactive').map(tag => ( | ||||||
|  |                 <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> | ||||||
|  |               ))} | ||||||
|  |             </TextField> | ||||||
|  |           </Box> | ||||||
|  |         </Box> | ||||||
|  |  | ||||||
|         {/* Específicos de variante */} |         <Box display="flex" gap={2}> | ||||||
|         <Grid item xs={12} md={4}> |           {/* Price */} | ||||||
|           <TextField |           <Box flex={1}> | ||||||
|             select |             <TextField label="Price" type="number" fullWidth value={form.price} onChange={(e) => setVal('price', e.target.value)} disabled={viewOnly} /> | ||||||
|             label="Color" |           </Box> | ||||||
|             fullWidth |           {/* Stock */} | ||||||
|             value={form.color} |           <Box flex={1}> | ||||||
|             onChange={(e) => setVal('color', e.target.value)} |             <TextField label="Stock" type="number" fullWidth value={form.stock} onChange={(e) => setVal('stock', e.target.value)} disabled={viewOnly} /> | ||||||
|           > |           </Box> | ||||||
|             {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} |           {/* Currency */} | ||||||
|             {!loadingTags && options.colors.filter(t => t.status !== 'Inactive').map(tag => ( |           <Box flex={1}> | ||||||
|               <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> |             <TextField | ||||||
|             ))} |               select | ||||||
|           </TextField> |               label="Currency" | ||||||
|         </Grid> |               fullWidth | ||||||
|  |               value={form.currency} | ||||||
|  |               onChange={(e) => setVal('currency', e.target.value)} | ||||||
|  |               disabled={viewOnly} | ||||||
|  |             > | ||||||
|  |               {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} | ||||||
|  |               {!loadingTags && options.currencies.filter(t => t.status !== 'Inactive').map(tag => ( | ||||||
|  |                 <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> | ||||||
|  |               ))} | ||||||
|  |             </TextField> | ||||||
|  |           </Box> | ||||||
|  |         </Box> | ||||||
|  |  | ||||||
|         <Grid item xs={12} md={4}> |         {/* Attributes */} | ||||||
|           <TextField |         <TextField | ||||||
|             select |           select | ||||||
|             label="Line" |           label="Legs" | ||||||
|             fullWidth |           fullWidth | ||||||
|             value={form.line} |           value={form.attributes.legs} | ||||||
|             onChange={(e) => setVal('line', e.target.value)} |           onChange={(e) => setVal('attributes.legs', e.target.value)} | ||||||
|           > |           disabled={viewOnly} | ||||||
|             {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} |         > | ||||||
|             {!loadingTags && options.lines.filter(t => t.status !== 'Inactive').map(tag => ( |           {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} | ||||||
|               <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> |           {!loadingTags && options.legs.filter(t => t.status !== 'Inactive').map(tag => ( | ||||||
|             ))} |             <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> | ||||||
|           </TextField> |           ))} | ||||||
|         </Grid> |         </TextField> | ||||||
|  |         <TextField | ||||||
|         <Grid item xs={12} md={4}> |           select | ||||||
|           <TextField |           label="Origin" | ||||||
|             select |           fullWidth | ||||||
|             label="Currency" |           value={form.attributes.origin} | ||||||
|             fullWidth |           onChange={(e) => setVal('attributes.origin', e.target.value)} | ||||||
|             value={form.currency} |           disabled={viewOnly} | ||||||
|             onChange={(e) => setVal('currency', e.target.value)} |         > | ||||||
|           > |           {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} | ||||||
|             {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} |           {!loadingTags && options.origins.filter(t => t.status !== 'Inactive').map(tag => ( | ||||||
|             {!loadingTags && options.currencies.filter(t => t.status !== 'Inactive').map(tag => ( |             <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> | ||||||
|               <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> |           ))} | ||||||
|             ))} |         </TextField> | ||||||
|           </TextField> |  | ||||||
|         </Grid> |  | ||||||
|  |  | ||||||
|         {/* Atributos como catálogos */} |  | ||||||
|         <Grid item xs={12} md={4}> |  | ||||||
|           <TextField |  | ||||||
|             select |  | ||||||
|             label="Material" |  | ||||||
|             fullWidth |  | ||||||
|             value={form.attributes.material} |  | ||||||
|             onChange={(e) => setVal('attributes.material', e.target.value)} |  | ||||||
|           > |  | ||||||
|             {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} |  | ||||||
|             {!loadingTags && options.materials.filter(t => t.status !== 'Inactive').map(tag => ( |  | ||||||
|               <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> |  | ||||||
|             ))} |  | ||||||
|           </TextField> |  | ||||||
|         </Grid> |  | ||||||
|  |  | ||||||
|         <Grid item xs={12} md={4}> |  | ||||||
|           <TextField |  | ||||||
|             select |  | ||||||
|             label="Legs" |  | ||||||
|             fullWidth |  | ||||||
|             value={form.attributes.legs} |  | ||||||
|             onChange={(e) => setVal('attributes.legs', e.target.value)} |  | ||||||
|           > |  | ||||||
|             {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} |  | ||||||
|             {!loadingTags && options.legs.filter(t => t.status !== 'Inactive').map(tag => ( |  | ||||||
|               <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> |  | ||||||
|             ))} |  | ||||||
|           </TextField> |  | ||||||
|         </Grid> |  | ||||||
|  |  | ||||||
|         <Grid item xs={12} md={4}> |  | ||||||
|           <TextField |  | ||||||
|             select |  | ||||||
|             label="Origin" |  | ||||||
|             fullWidth |  | ||||||
|             value={form.attributes.origin} |  | ||||||
|             onChange={(e) => setVal('attributes.origin', e.target.value)} |  | ||||||
|           > |  | ||||||
|             {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>} |  | ||||||
|             {!loadingTags && options.origins.filter(t => t.status !== 'Inactive').map(tag => ( |  | ||||||
|               <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem> |  | ||||||
|             ))} |  | ||||||
|           </TextField> |  | ||||||
|         </Grid> |  | ||||||
|  |  | ||||||
|         {/* Números */} |  | ||||||
|         <Grid item xs={12} md={4}> |  | ||||||
|           <TextField label="Stock" type="number" fullWidth value={form.stock} onChange={(e) => setVal('stock', e.target.value)} /> |  | ||||||
|         </Grid> |  | ||||||
|         <Grid item xs={12} md={4}> |  | ||||||
|           <TextField label="Price" type="number" fullWidth value={form.price} onChange={(e) => setVal('price', e.target.value)} /> |  | ||||||
|         </Grid> |  | ||||||
|  |  | ||||||
|         {/* Status */} |         {/* Status */} | ||||||
|         <Grid item xs={12} md={4}> |         <TextField select label="Status" fullWidth value={form.status} onChange={(e) => setVal('status', e.target.value)} disabled={viewOnly}> | ||||||
|           <TextField select label="Status" fullWidth value={form.status} onChange={(e) => setVal('status', e.target.value)}> |           <MenuItem value="Active">Active</MenuItem> | ||||||
|             <MenuItem value="Active">Active</MenuItem> |           <MenuItem value="Inactive">Inactive</MenuItem> | ||||||
|             <MenuItem value="Inactive">Inactive</MenuItem> |         </TextField> | ||||||
|           </TextField> |       </Box> | ||||||
|         </Grid> |  | ||||||
|       </Grid> |  | ||||||
|  |  | ||||||
|       <Box display="flex" justifyContent="flex-end" mt={3} gap={1}> |       <Box display="flex" justifyContent="flex-end" mt={3} gap={1}> | ||||||
|         <Button onClick={onCancel} className="button-transparent">Cancel</Button> |         <Button onClick={onCancel} className="button-transparent">{viewOnly ? 'Close' : 'Cancel'}</Button> | ||||||
|         <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> |         {!viewOnly && ( | ||||||
|  |           <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> | ||||||
|  |         )} | ||||||
|       </Box> |       </Box> | ||||||
|     </Box> |     </Box> | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -3,11 +3,12 @@ import { useEffect, useMemo, useState } from 'react'; | |||||||
| import { DataGrid } from '@mui/x-data-grid'; | import { DataGrid } from '@mui/x-data-grid'; | ||||||
| import { | import { | ||||||
|   Typography, Button, Dialog, DialogTitle, DialogContent, |   Typography, Button, Dialog, DialogTitle, DialogContent, | ||||||
|   IconButton, Box, FormControlLabel, Switch, Tooltip |   IconButton, Box, ToggleButtonGroup, ToggleButton | ||||||
| } from '@mui/material'; | } from '@mui/material'; | ||||||
| import EditRoundedIcon from '@mui/icons-material/EditRounded'; | import EditRoundedIcon from '@mui/icons-material/EditRounded'; | ||||||
| import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | ||||||
| import AddRoundedIcon from '@mui/icons-material/AddRounded'; | import AddRoundedIcon from '@mui/icons-material/AddRounded'; | ||||||
|  | import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded'; | ||||||
| import AddOrEditProductCollectionForm from './AddOrEditProductCollectionForm'; | import AddOrEditProductCollectionForm from './AddOrEditProductCollectionForm'; | ||||||
| import FurnitureVariantApi from '../../../api/ProductsApi'; | import FurnitureVariantApi from '../../../api/ProductsApi'; | ||||||
| import CategoriesApi from '../../../api/CategoriesApi'; | import CategoriesApi from '../../../api/CategoriesApi'; | ||||||
| @@ -42,15 +43,20 @@ export default function ProductCollections() { | |||||||
|   const tagTypeApi = useMemo(() => new TagTypeApi(token), [token]); |   const tagTypeApi = useMemo(() => new TagTypeApi(token), [token]); | ||||||
|   const categoriesApi = useMemo(() => new CategoriesApi(token), [token]); |   const categoriesApi = useMemo(() => new CategoriesApi(token), [token]); | ||||||
|  |  | ||||||
|   const toast = useApiToast(); |   const { handleError } = useApiToast(); | ||||||
|  |  | ||||||
|   const [rows, setRows] = useState([]); |   const [rows, setRows] = useState([]); | ||||||
|   const [rawRows, setRawRows] = useState([]); |   const [rawRows, setRawRows] = useState([]); | ||||||
|   const [open, setOpen] = useState(false); |   const [open, setOpen] = useState(false); | ||||||
|   const [editRow, setEditRow] = useState(null); |   const [editRow, setEditRow] = useState(null); | ||||||
|   const [showInactive, setShowInactive] = useState(false); |  | ||||||
|   const [loading, setLoading] = useState(true); |   const [loading, setLoading] = useState(true); | ||||||
|  |  | ||||||
|  |   const [statusFilter, setStatusFilter] = useState('All'); | ||||||
|  |  | ||||||
|  |   const [viewOnly, setViewOnly] = useState(false); | ||||||
|  |  | ||||||
|  |   const [confirmOpen, setConfirmOpen] = useState(false); | ||||||
|  |  | ||||||
|   // Tags |   // Tags | ||||||
|   const [loadingTags, setLoadingTags] = useState(true); |   const [loadingTags, setLoadingTags] = useState(true); | ||||||
|   const [typeMap, setTypeMap] = useState({}); |   const [typeMap, setTypeMap] = useState({}); | ||||||
| @@ -80,15 +86,6 @@ export default function ProductCollections() { | |||||||
|     }; |     }; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const labelCategory = useMemo(() => buildLabelResolver(TYPE_NAMES.category), [byType]); |  | ||||||
|   const labelProvider = useMemo(() => buildLabelResolver(TYPE_NAMES.provider), [byType]); |  | ||||||
|   const labelColor = useMemo(() => buildLabelResolver(TYPE_NAMES.color), [byType]); |  | ||||||
|   const labelLine = useMemo(() => buildLabelResolver(TYPE_NAMES.line), [byType]); |  | ||||||
|   const labelCurrency = useMemo(() => buildLabelResolver(TYPE_NAMES.currency), [byType]); |  | ||||||
|   const labelMaterial = useMemo(() => buildLabelResolver(TYPE_NAMES.material), [byType]); |  | ||||||
|   const labelLegs = useMemo(() => buildLabelResolver(TYPE_NAMES.legs), [byType]); |  | ||||||
|   const labelOrigin = useMemo(() => buildLabelResolver(TYPE_NAMES.origin), [byType]); |  | ||||||
|  |  | ||||||
|   // Cargar TagTypes + Tags |   // Cargar TagTypes + Tags | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     let mounted = true; |     let mounted = true; | ||||||
| @@ -154,10 +151,9 @@ export default function ProductCollections() { | |||||||
|         createdBy: r.createdBy ?? null, |         createdBy: r.createdBy ?? null, | ||||||
|       })); |       })); | ||||||
|       setRawRows(normalized); |       setRawRows(normalized); | ||||||
|       setRows(normalized.filter(r => showInactive ? true : r.status !== 'Inactive')); |  | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       console.error(err); |       console.error(err); | ||||||
|       toast.error(err?.message || 'Error loading variants'); |       handleError(err, 'Error loading product collections'); | ||||||
|     } finally { |     } finally { | ||||||
|       setLoading(false); |       setLoading(false); | ||||||
|     } |     } | ||||||
| @@ -165,108 +161,215 @@ export default function ProductCollections() { | |||||||
|  |  | ||||||
|   useEffect(() => { load(); /* eslint-disable-next-line */ }, []); |   useEffect(() => { load(); /* eslint-disable-next-line */ }, []); | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setRows(rawRows.filter(r => showInactive ? true : r.status !== 'Inactive')); |     if (statusFilter === 'All') { | ||||||
|   }, [showInactive, rawRows]); |       setRows(rawRows); | ||||||
|  |     } else { | ||||||
|  |       const want = statusFilter.toLowerCase(); | ||||||
|  |       setRows(rawRows.filter(r => String(r.status ?? 'Active').toLowerCase() === want)); | ||||||
|  |     } | ||||||
|  |   }, [statusFilter, rawRows]); | ||||||
|  |  | ||||||
|  |   const handleDeleteClick = (row) => { | ||||||
|  |     setEditRow(row); | ||||||
|  |     setConfirmOpen(true); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const confirmDelete = async () => { | ||||||
|  |     try { | ||||||
|  |       if (!editRow?.id) return; | ||||||
|  |       await api.changeStatusVariant({ mongoId: editRow._Id, status: 'Inactive' }); | ||||||
|  |       await load(); | ||||||
|  |     } catch (err) { | ||||||
|  |       console.error(err); | ||||||
|  |     } finally { | ||||||
|  |       setConfirmOpen(false); | ||||||
|  |       setEditRow(null); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const columns = [ |   const columns = [ | ||||||
|     { field: 'modelId', headerName: 'Model Id', width: 220 }, |     { | ||||||
|     { field: 'name', headerName: 'Name', width: 200 }, |       field: 'actions', | ||||||
|     { field: 'categoryId', headerName: 'Category', width: 170, valueGetter: (p) => labelCategory(p?.row?.categoryId) }, |       headerName: '', | ||||||
|     { field: 'providerId', headerName: 'Provider', width: 170, valueGetter: (p) => labelProvider(p?.row?.providerId) }, |       width: 190, | ||||||
|     { field: 'color', headerName: 'Color', width: 130, valueGetter: (p) => labelColor(p?.row?.color) }, |       sortable: false, | ||||||
|     { field: 'line', headerName: 'Line', width: 130, valueGetter: (p) => labelLine(p?.row?.line) }, |       filterable: false, | ||||||
|  |       disableExport: true, | ||||||
|  |       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={() => { setEditRow(params.row); setViewOnly(false); setOpen(true); }} | ||||||
|  |           > | ||||||
|  |             <EditRoundedIcon fontSize="small" /> | ||||||
|  |           </IconButton> | ||||||
|  |           <IconButton | ||||||
|  |             size="small" | ||||||
|  |             sx={{ | ||||||
|  |               backgroundColor: '#E3F2FD', | ||||||
|  |               color: '#1565C0', | ||||||
|  |               '&:hover': { backgroundColor: '#BBDEFB' }, | ||||||
|  |               borderRadius: 2, | ||||||
|  |               p: 1, | ||||||
|  |             }} | ||||||
|  |             onClick={() => { setEditRow(params.row); setOpen(true); setViewOnly(true); }} | ||||||
|  |           > | ||||||
|  |             <VisibilityRoundedIcon fontSize="small" /> | ||||||
|  |           </IconButton> | ||||||
|  |           <IconButton | ||||||
|  |             size="small" | ||||||
|  |             sx={{ | ||||||
|  |               backgroundColor: '#FBE9E7', | ||||||
|  |               color: '#C62828', | ||||||
|  |               '&:hover': { backgroundColor: '#EF9A9A' }, | ||||||
|  |               borderRadius: 2, | ||||||
|  |               p: 1, | ||||||
|  |             }} | ||||||
|  |             onClick={() => { setViewOnly(false); handleDeleteClick(params?.row); }} | ||||||
|  |           > | ||||||
|  |             <DeleteRoundedIcon fontSize="small" /> | ||||||
|  |           </IconButton> | ||||||
|  |         </Box> | ||||||
|  |       ), | ||||||
|  |     }, | ||||||
|  |     { field: 'name', headerName: 'Name', flex: 1, minWidth: 160 }, | ||||||
|  |     { field: 'categoryId', headerName: 'Category', flex: 1, minWidth: 160 }, | ||||||
|  |     { field: 'providerId', headerName: 'Provider', flex: 1, minWidth: 160 }, | ||||||
|  |     { field: 'color', headerName: 'Color', flex: 1, minWidth: 160 }, | ||||||
|  |     { field: 'line', headerName: 'Line', flex: 1, minWidth: 160 }, | ||||||
|     { |     { | ||||||
|       field: 'price', |       field: 'price', | ||||||
|       headerName: 'Price', |       headerName: 'Price', | ||||||
|       width: 130, |       flex: 1, | ||||||
|  |       minWidth: 160, | ||||||
|       type: 'number', |       type: 'number', | ||||||
|       valueGetter: (p) => parsePrice(p?.row?.price), |       valueGetter: (p) => parsePrice(p?.row?.price), | ||||||
|       renderCell: (p) => { |       renderCell: (p) => { | ||||||
|         const currency = labelCurrency(p?.row?.currency || 'USD'); |  | ||||||
|         const val = parsePrice(p?.row?.price); |         const val = parsePrice(p?.row?.price); | ||||||
|  |         const currency = p?.row?.currency || 'USD'; | ||||||
|         try { |         try { | ||||||
|           return new Intl.NumberFormat(undefined, { style: 'currency', currency: currency || 'USD' }).format(val); |           return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(val); | ||||||
|         } catch { |         } catch { | ||||||
|           return `${currency} ${val.toFixed(2)}`; |           return `${currency} ${val.toFixed(2)}`; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     { field: 'currency', headerName: 'Currency', width: 120, valueGetter: (p) => labelCurrency(p?.row?.currency) }, |     { field: 'currency', headerName: 'Currency', flex: 1, minWidth: 160 }, | ||||||
|     { field: 'stock', headerName: 'Stock', width: 100, type: 'number', valueGetter: (p) => Number(p?.row?.stock ?? 0) }, |     { field: 'stock', headerName: 'Stock', flex: 1, minWidth: 160 }, | ||||||
|     { field: 'attributes.material', headerName: 'Material', width: 150, valueGetter: (p) => labelMaterial(p?.row?.attributes?.material) }, |  | ||||||
|     { field: 'attributes.legs', headerName: 'Legs', width: 140, valueGetter: (p) => labelLegs(p?.row?.attributes?.legs) }, |  | ||||||
|     { field: 'attributes.origin', headerName: 'Origin', width: 150, valueGetter: (p) => labelOrigin(p?.row?.attributes?.origin) }, |  | ||||||
|     { field: 'status', headerName: 'Status', width: 120 }, |  | ||||||
|     { |     { | ||||||
|       field: 'actions', |       field: 'attributes', | ||||||
|       headerName: '', |       headerName: 'Attributes', | ||||||
|  |       minWidth: 300, | ||||||
|  |       flex: 1.5, | ||||||
|       sortable: false, |       sortable: false, | ||||||
|       width: 110, |       filterable: false, | ||||||
|       renderCell: (p) => ( |       renderCell: (params) => { | ||||||
|         <Box display="flex" gap={1}> |         const a = params?.row?.attributes || {}; | ||||||
|           <Tooltip title="Edit"> |         const chips = Object.entries(a).filter(([, v]) => !!v); | ||||||
|             <IconButton size="small" onClick={() => { setEditRow(p.row); setOpen(true); }}> |         return ( | ||||||
|               <EditRoundedIcon fontSize="small" /> |           <Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}> | ||||||
|             </IconButton> |             {chips.map(([key, value]) => ( | ||||||
|           </Tooltip> |               <Box | ||||||
|           <Tooltip title={p.row.status === 'Active' ? 'Deactivate' : 'Activate'}> |                 key={key} | ||||||
|             <IconButton |                 component="span" | ||||||
|               size="small" |                 sx={{ | ||||||
|               onClick={async () => { |                   px: 1.5, | ||||||
|                 try { |                   py: 0.5, | ||||||
|                   const updated = { ...p.row, status: p.row.status === 'Active' ? 'Inactive' : 'Active' }; |                   borderRadius: '12px', | ||||||
|                   await api.updateVariant(updated); |                   backgroundColor: '#DFCCBC', | ||||||
|                   setRawRows(prev => prev.map(r => r.id === p.row.id ? updated : r)); |                   fontSize: 12, | ||||||
|                 } catch (err) { |                   color: '#26201A', | ||||||
|                   console.error(err); |                   lineHeight: '18px', | ||||||
|                   toast.error(err?.message || 'Error updating status'); |                 }} | ||||||
|                 } |               > | ||||||
|               }} |                 {key}: {value} | ||||||
|             > |               </Box> | ||||||
|               <DeleteRoundedIcon fontSize="small" /> |             ))} | ||||||
|             </IconButton> |           </Box> | ||||||
|           </Tooltip> |         ); | ||||||
|         </Box> |       } | ||||||
|       ) |  | ||||||
|     }, |     }, | ||||||
|  |     { field: 'status', headerName: 'Status', flex: 1, minWidth: 160 } | ||||||
|   ]; |   ]; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <SectionContainer> |     <Box | ||||||
|       <Box display="flex" alignItems="center" justifyContent="space-between" mb={2}> |       sx={{ | ||||||
|         <Typography variant="h6">Furniture Variants</Typography> |         height: 'calc(100vh - 64px - 64px)', | ||||||
|  |         display: 'flex', | ||||||
|  |         flexDirection: 'column', | ||||||
|  |         gap: 2, | ||||||
|  |       }} | ||||||
|  |     > | ||||||
|  |       <Box display="flex" alignItems="center" justifyContent="space-between" mb={2} flexWrap="wrap" gap={2}> | ||||||
|  |         <Typography color='text.primary' variant="h6">Product Collection</Typography> | ||||||
|         <Box display="flex" alignItems="center" gap={2}> |         <Box display="flex" alignItems="center" gap={2}> | ||||||
|           <FormControlLabel control={<Switch checked={showInactive} onChange={(_, v) => setShowInactive(v)} />} label="Show Inactive" /> |           <ToggleButtonGroup | ||||||
|           <Button variant="contained" className="button-gold" startIcon={<AddRoundedIcon />} onClick={() => { setEditRow(null); setOpen(true); }}> |             value={statusFilter} | ||||||
|             Add Variant |             exclusive | ||||||
|  |             onChange={(_, v) => v && setStatusFilter(v)} | ||||||
|  |             size="small" | ||||||
|  |           > | ||||||
|  |             <ToggleButton value="Active">Active</ToggleButton> | ||||||
|  |             <ToggleButton value="All">All</ToggleButton> | ||||||
|  |             <ToggleButton value="Inactive">Inactive</ToggleButton> | ||||||
|  |           </ToggleButtonGroup> | ||||||
|  |           <Button variant="contained" className="button-gold" startIcon={<AddRoundedIcon />} onClick={() => { setEditRow(null); setViewOnly(false); setOpen(true); }}> | ||||||
|  |             Add Product Collection | ||||||
|           </Button> |           </Button> | ||||||
|         </Box> |         </Box> | ||||||
|       </Box> |       </Box> | ||||||
|  |  | ||||||
|       <Box sx={{ width: '100%', height: 560 }}> |       <Box sx={{ flex: 1, minHeight: 0 }}> | ||||||
|         <DataGrid |         <DataGrid | ||||||
|           rows={rows} |           rows={rows} | ||||||
|           columns={columns} |           columns={columns} | ||||||
|           disableRowSelectionOnClick |           disableRowSelectionOnClick | ||||||
|           loading={loading || loadingTags} |           loading={loading || loadingTags} | ||||||
|           pageSizeOptions={[10, 25, 50]} |           pageSizeOptions={[50, 100, 200]} | ||||||
|           initialState={{ |           initialState={{ | ||||||
|             pagination: { paginationModel: { pageSize: 10 } }, |             pagination: { paginationModel: { pageSize: 50 } }, | ||||||
|             columns: { columnVisibilityModel: { id: false, _Id: false } }, |             columns: { columnVisibilityModel: { id: false, _Id: false } }, | ||||||
|           }} |           }} | ||||||
|           getRowHeight={() => 'auto'} |           getRowHeight={() => 'auto'} | ||||||
|  |           columnBuffer={0} | ||||||
|           sx={{ |           sx={{ | ||||||
|             '& .MuiDataGrid-cell': { display: 'flex', alignItems: 'center' }, |             height: '100%', | ||||||
|             '& .MuiDataGrid-columnHeader': { display: 'flex', alignItems: 'center' }, |             '& .MuiDataGrid-cell, & .MuiDataGrid-columnHeader': { | ||||||
|  |               display: 'flex', | ||||||
|  |               alignItems: 'center', | ||||||
|  |             }, | ||||||
|  |             '& .MuiDataGrid-filler': { | ||||||
|  |               display: 'none', | ||||||
|  |             }, | ||||||
|  |           }} | ||||||
|  |           slots={{ | ||||||
|  |             noRowsOverlay: () => ( | ||||||
|  |               <Box sx={{ p: 2 }}>No product collection found. Try switching the status filter to "All".</Box> | ||||||
|  |             ), | ||||||
|           }} |           }} | ||||||
|         /> |         /> | ||||||
|       </Box> |       </Box> | ||||||
|  |  | ||||||
|       <Dialog open={open} onClose={() => setOpen(false)} maxWidth="md" fullWidth> |       <Dialog open={open} onClose={() => setOpen(false)} maxWidth="md" fullWidth> | ||||||
|         <DialogTitle>{editRow ? 'Edit Product Collection' : 'Add Product Collection'}</DialogTitle> |         <DialogTitle> | ||||||
|  |           {viewOnly | ||||||
|  |             ? 'View Product Collection' | ||||||
|  |             : editRow | ||||||
|  |               ? 'Edit Product Collection' | ||||||
|  |               : 'Add Product Collection'} | ||||||
|  |         </DialogTitle> | ||||||
|         <DialogContent> |         <DialogContent> | ||||||
|           <AddOrEditProductCollectionForm |           <AddOrEditProductCollectionForm | ||||||
|             initialData={editRow} |             initialData={editRow} | ||||||
|  |             viewOnly={viewOnly} | ||||||
|             onAdd={(saved) => { |             onAdd={(saved) => { | ||||||
|               setOpen(false); |               setOpen(false); | ||||||
|               if (editRow) { |               if (editRow) { | ||||||
| @@ -279,6 +382,16 @@ export default function ProductCollections() { | |||||||
|           /> |           /> | ||||||
|         </DialogContent> |         </DialogContent> | ||||||
|       </Dialog> |       </Dialog> | ||||||
|     </SectionContainer> |  | ||||||
|  |       <Dialog open={confirmOpen} onClose={() => setConfirmOpen(false)}> | ||||||
|  |         <DialogTitle>Delete Product Collection</DialogTitle> | ||||||
|  |         <DialogContent> | ||||||
|  |           <Box sx={{ display: 'flex', gap: 1, mt: 2, mb: 1 }}> | ||||||
|  |             <Button onClick={() => setConfirmOpen(false)} className='button-transparent'>Cancel</Button> | ||||||
|  |             <Button color="error" variant="contained" onClick={confirmDelete} className="button-gold">Delete</Button> | ||||||
|  |           </Box> | ||||||
|  |         </DialogContent> | ||||||
|  |       </Dialog> | ||||||
|  |     </Box> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -102,7 +102,7 @@ export default function UserManagement() { | |||||||
|             status: r.status ?? 'Active', |             status: r.status ?? 'Active', | ||||||
|             companies: Array.isArray(r.companies) ? r.companies : [], |             companies: Array.isArray(r.companies) ? r.companies : [], | ||||||
|             projects: Array.isArray(r.projects) ? r.projects : [], |             projects: Array.isArray(r.projects) ? r.projects : [], | ||||||
|           }; |         }; | ||||||
|         setEditingData(normalized); |         setEditingData(normalized); | ||||||
|         setOpen(true); |         setOpen(true); | ||||||
|     }; |     }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user