chore: edit existing item
This commit is contained in:
		| @@ -38,6 +38,7 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | ||||
|   const [form, setForm] = useState({ | ||||
|     _Id: '', | ||||
|     id: '', | ||||
|     tenantId: '', | ||||
|     tagName: '', | ||||
|     typeId: '', | ||||
|     parentTagId: [], | ||||
| @@ -45,6 +46,10 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | ||||
|     displayOrder: 0, | ||||
|     icon: '', | ||||
|     status: 'Active', | ||||
|     createdAt: null, | ||||
|     createdBy: null, | ||||
|     updatedAt: null, | ||||
|     updatedBy: null, | ||||
|   }); | ||||
|  | ||||
|   // cargar tipos y tags para selects | ||||
| @@ -68,6 +73,7 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | ||||
|       setForm({ | ||||
|         _Id, | ||||
|         id, | ||||
|         tenantId: initialData.tenantId || extractTenantId(token) || '', | ||||
|         tagName: initialData.tagName || initialData.name || '', | ||||
|         typeId: initialData.typeId || '', | ||||
|         parentTagId: Array.isArray(initialData.parentTagId) ? initialData.parentTagId : [], | ||||
| @@ -75,11 +81,16 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | ||||
|         displayOrder: Number(initialData.displayOrder ?? 0), | ||||
|         icon: initialData.icon || '', | ||||
|         status: initialData.status || 'Active', | ||||
|         createdAt: initialData.createdAt ?? null, | ||||
|         createdBy: initialData.createdBy ?? null, | ||||
|         updatedAt: initialData.updatedAt ?? null, | ||||
|         updatedBy: initialData.updatedBy ?? null, | ||||
|       }); | ||||
|     } else { | ||||
|       setForm({ | ||||
|         _Id: '', | ||||
|         id: '', | ||||
|         tenantId: extractTenantId(token) || '', | ||||
|         tagName: '', | ||||
|         typeId: '', | ||||
|         parentTagId: [], | ||||
| @@ -87,6 +98,10 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | ||||
|         displayOrder: 0, | ||||
|         icon: '', | ||||
|         status: 'Active', | ||||
|         createdAt: null, | ||||
|         createdBy: null, | ||||
|         updatedAt: null, | ||||
|         updatedBy: null, | ||||
|       }); | ||||
|     } | ||||
|   }, [initialData]); | ||||
| @@ -140,12 +155,44 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const handleDelete = async () => { | ||||
|     try { | ||||
|       // Try to use Mongo _Id (24-hex); if not present, fall back to GUID `id`. | ||||
|       const hex = typeof form._Id === 'string' && /^[0-9a-f]{24}$/i.test(form._Id) ? form._Id : null; | ||||
|       const idToUse = hex || form.id; | ||||
|       if (!idToUse) throw new Error('Missing id to delete'); | ||||
|       await api.changeStatus({ id: idToUse, status: 'Inactive' }); | ||||
|       onAdd?.(); | ||||
|     } catch (e) { | ||||
|       console.error('Delete category failed:', e); | ||||
|       alert(e.message || 'Delete failed'); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <Paper sx={{ p: 2 }}> | ||||
|       <Typography variant="subtitle1" sx={{ mb: 2 }}> | ||||
|         {form._Id ? 'Edit Category' : 'Add Category'} | ||||
|       </Typography> | ||||
|  | ||||
|       {/* IDs (read-only) */} | ||||
|       {form._Id || form.id ? ( | ||||
|         <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2, mb: 2 }}> | ||||
|           <TextField label="_Id" value={form._Id} InputProps={{ readOnly: true }} fullWidth /> | ||||
|           <TextField label="id" value={form.id} InputProps={{ readOnly: true }} fullWidth /> | ||||
|         </Box> | ||||
|       ) : null} | ||||
|  | ||||
|       {/* Tenant (read-only; comes from token or existing record) */} | ||||
|       <TextField | ||||
|         name="tenantId" | ||||
|         label="Tenant Id" | ||||
|         value={form.tenantId} | ||||
|         fullWidth | ||||
|         sx={{ mb: 2 }} | ||||
|         InputProps={{ readOnly: true }} | ||||
|       /> | ||||
|  | ||||
|       <TextField | ||||
|         name="tagName" | ||||
|         label="Name" | ||||
| @@ -239,9 +286,23 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | ||||
|         <MenuItem value="Inactive">Inactive</MenuItem> | ||||
|       </TextField> | ||||
|  | ||||
|       <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> | ||||
|       {form._Id || form.id ? ( | ||||
|         <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2, mt: 2 }}> | ||||
|           <TextField label="Created At" value={form.createdAt ? new Date(form.createdAt).toLocaleString() : '—'} InputProps={{ readOnly: true }} fullWidth /> | ||||
|           <TextField label="Created By" value={form.createdBy ?? '—'} InputProps={{ readOnly: true }} fullWidth /> | ||||
|           <TextField label="Updated At" value={form.updatedAt ? new Date(form.updatedAt).toLocaleString() : '—'} InputProps={{ readOnly: true }} fullWidth /> | ||||
|           <TextField label="Updated By" value={form.updatedBy ?? '—'} InputProps={{ readOnly: true }} fullWidth /> | ||||
|         </Box> | ||||
|       ) : null} | ||||
|  | ||||
|       <Box display="flex" justifyContent="space-between" gap={1} mt={3}> | ||||
|         { (form._Id || form.id) ? ( | ||||
|           <Button color="error" onClick={handleDelete}>Delete</Button> | ||||
|         ) : <span /> } | ||||
|         <Box sx={{ display: 'flex', gap: 1 }}> | ||||
|           <Button onClick={onCancel} className="button-transparent">Cancel</Button> | ||||
|           <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> | ||||
|         </Box> | ||||
|       </Box> | ||||
|     </Paper> | ||||
|   ); | ||||
|   | ||||
| @@ -2,8 +2,8 @@ import { useEffect, useMemo, useRef, useState } from 'react'; | ||||
| import { Box, Button, Dialog, DialogContent, DialogTitle, IconButton, Typography, | ||||
|          ToggleButton, ToggleButtonGroup } from '@mui/material'; | ||||
| import { DataGrid } from '@mui/x-data-grid'; | ||||
| import EditIcon from '@mui/icons-material/Edit'; | ||||
| import DeleteIcon from '@mui/icons-material/Delete'; | ||||
| import EditRoundedIcon from '@mui/icons-material/EditRounded'; | ||||
| import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | ||||
| import AddOrEditCategoryForm from './AddOrEditCategoryForm'; | ||||
| import CategoriesApi from '../../api/CategoriesApi'; | ||||
| import { useAuth } from '../../context/AuthContext'; | ||||
| @@ -14,7 +14,7 @@ export default function Categories() { | ||||
|   const api = useMemo(() => new CategoriesApi(token), [token]); | ||||
|  | ||||
|   const [rows, setRows] = useState([]); | ||||
|   const [statusFilter, setStatusFilter] = useState('Active'); // <- por defecto Active | ||||
|   const [statusFilter, setStatusFilter] = useState('All'); // <- por defecto All | ||||
|   const [open, setOpen] = useState(false); | ||||
|   const [editingCategory, setEditingCategory] = useState(null); | ||||
|   const [confirmOpen, setConfirmOpen] = useState(false); | ||||
| @@ -31,13 +31,8 @@ export default function Categories() { | ||||
|   const loadData = async () => { | ||||
|     try { | ||||
|       const data = await api.getAll(); | ||||
|       const safeRows = (Array.isArray(data) ? data : []) | ||||
|         .filter(Boolean) | ||||
|         .map((r, idx) => ({ | ||||
|           ...r, | ||||
|           __rid: r?._id || r?._Id || r?.id || r?.Id || `tmp-${idx}`, | ||||
|         })); | ||||
|       setRows(safeRows); | ||||
|       const list = Array.isArray(data) ? data : []; | ||||
|       setRows(list); | ||||
|     } catch (e) { | ||||
|       console.error('Failed to load categories:', e); | ||||
|       setRows([]); | ||||
| @@ -110,48 +105,79 @@ export default function Categories() { | ||||
|   }, [rows, statusFilter]); | ||||
|  | ||||
|   const columns = [ | ||||
|     { | ||||
|       field: 'tagName', | ||||
|       headerName: 'Name', | ||||
|       flex: 1, | ||||
|       minWidth: 200, | ||||
|       valueGetter: (p) => p?.row?.tagName ?? p?.row?.name ?? '', | ||||
|     }, | ||||
|     { | ||||
|       field: 'slug', | ||||
|       headerName: 'Slug', | ||||
|       width: 180, | ||||
|       valueGetter: (p) => p?.row?.slug ?? '', | ||||
|     }, | ||||
|     { | ||||
|       field: 'displayOrder', | ||||
|       headerName: 'Display', | ||||
|       width: 120, | ||||
|       valueGetter: (p) => Number(p?.row?.displayOrder ?? 0), | ||||
|     }, | ||||
|     { | ||||
|       field: 'status', | ||||
|       headerName: 'Status', | ||||
|       width: 140, | ||||
|       valueGetter: (p) => p?.row?.status ?? 'Active', | ||||
|     }, | ||||
|     { | ||||
|       field: 'actions', | ||||
|       headerName: '', | ||||
|       width: 120, | ||||
|       width: 130, | ||||
|       sortable: false, | ||||
|       filterable: false, | ||||
|       renderCell: (params) => { | ||||
|         const r = params?.row; | ||||
|         if (!r) return null; | ||||
|         return ( | ||||
|           <Box sx={{ display: 'flex', gap: 1 }}> | ||||
|             <IconButton size="small" onClick={() => handleEditClick(params)}><EditIcon /></IconButton> | ||||
|             <IconButton size="small" color="error" onClick={() => handleDeleteClick(r)}><DeleteIcon /></IconButton> | ||||
|           </Box> | ||||
|         ); | ||||
|       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={() => 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> | ||||
|       ), | ||||
|     }, | ||||
|     { field: 'tenantId', headerName: 'tenantId', width: 180 }, | ||||
|     { field: 'tagName', headerName: 'tagName', width: 200 }, | ||||
|     { field: 'typeId', headerName: 'typeId', width: 260 }, | ||||
|     { | ||||
|       field: 'parentTagId', | ||||
|       headerName: 'parentTagId', | ||||
|       width: 220, | ||||
|       valueGetter: (params) => Array.isArray(params.value) ? params.value.join(', ') : (params.value ?? '—'), | ||||
|     }, | ||||
|     { field: 'slug', headerName: 'slug', width: 180 }, | ||||
|     { field: 'displayOrder', headerName: 'displayOrder', width: 140, type: 'number' }, | ||||
|     { field: 'icon', headerName: 'icon', width: 160 }, | ||||
|     { field: '_id', headerName: '_id', width: 260 }, | ||||
|     { field: 'id', headerName: 'id', width: 280 }, | ||||
|     { | ||||
|       field: 'createdAt', | ||||
|       headerName: 'createdAt', | ||||
|       width: 200, | ||||
|       valueFormatter: (p) => { | ||||
|         const v = p?.value; | ||||
|         return v ? new Date(v).toLocaleString() : '—'; | ||||
|       }, | ||||
|     }, | ||||
|     { field: 'createdBy', headerName: 'createdBy', width: 180 }, | ||||
|     { | ||||
|       field: 'updatedAt', | ||||
|       headerName: 'updatedAt', | ||||
|       width: 200, | ||||
|       valueFormatter: (p) => { | ||||
|         const v = p?.value; | ||||
|         return v ? new Date(v).toLocaleString() : '—'; | ||||
|       }, | ||||
|     }, | ||||
|     { field: 'updatedBy', headerName: 'updatedBy', width: 180 }, | ||||
|     { field: 'status', headerName: 'status', width: 140 }, | ||||
|   ]; | ||||
|  | ||||
|   return ( | ||||
| @@ -177,15 +203,29 @@ export default function Categories() { | ||||
|         </Box> | ||||
|       </Box> | ||||
|  | ||||
|       <DataGrid | ||||
|         rows={filteredRows} | ||||
|         columns={columns} | ||||
|         pageSize={10} | ||||
|         rowsPerPageOptions={[10]} | ||||
|         autoHeight | ||||
|         disableColumnMenu | ||||
|         getRowId={(r) => r?.__rid} | ||||
|       /> | ||||
|       <Box sx={{ width: '100%', overflowX: 'auto' }}> | ||||
|         <DataGrid | ||||
|           rows={filteredRows} | ||||
|           columns={columns} | ||||
|           initialState={{ pagination: { paginationModel: { pageSize: 10 } } }} | ||||
|           pageSizeOptions={[10]} | ||||
|           autoHeight | ||||
|           disableColumnMenu | ||||
|           getRowId={(r) => r?._id || r?.id} | ||||
|           sx={{ | ||||
|             minWidth: 1400, | ||||
|             '& .MuiDataGrid-cell, & .MuiDataGrid-columnHeader': { | ||||
|               display: 'flex', | ||||
|               alignItems: 'center', | ||||
|             }, | ||||
|           }} | ||||
|           slots={{ | ||||
|             noRowsOverlay: () => ( | ||||
|               <Box sx={{ p: 2 }}>No categories found. Try switching the status filter to "All".</Box> | ||||
|             ), | ||||
|           }} | ||||
|         /> | ||||
|       </Box> | ||||
|  | ||||
|       <Dialog open={open} onClose={() => { setOpen(false); setEditingCategory(null); }} fullWidth> | ||||
|         <DialogTitle>{editingCategory ? 'Edit Category' : 'Add Category'}</DialogTitle> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Rodolfo Ruiz
					Rodolfo Ruiz