chore: edit existing item
This commit is contained in:
		| @@ -38,6 +38,7 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | |||||||
|   const [form, setForm] = useState({ |   const [form, setForm] = useState({ | ||||||
|     _Id: '', |     _Id: '', | ||||||
|     id: '', |     id: '', | ||||||
|  |     tenantId: '', | ||||||
|     tagName: '', |     tagName: '', | ||||||
|     typeId: '', |     typeId: '', | ||||||
|     parentTagId: [], |     parentTagId: [], | ||||||
| @@ -45,6 +46,10 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | |||||||
|     displayOrder: 0, |     displayOrder: 0, | ||||||
|     icon: '', |     icon: '', | ||||||
|     status: 'Active', |     status: 'Active', | ||||||
|  |     createdAt: null, | ||||||
|  |     createdBy: null, | ||||||
|  |     updatedAt: null, | ||||||
|  |     updatedBy: null, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   // cargar tipos y tags para selects |   // cargar tipos y tags para selects | ||||||
| @@ -68,6 +73,7 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | |||||||
|       setForm({ |       setForm({ | ||||||
|         _Id, |         _Id, | ||||||
|         id, |         id, | ||||||
|  |         tenantId: initialData.tenantId || extractTenantId(token) || '', | ||||||
|         tagName: initialData.tagName || initialData.name || '', |         tagName: initialData.tagName || initialData.name || '', | ||||||
|         typeId: initialData.typeId || '', |         typeId: initialData.typeId || '', | ||||||
|         parentTagId: Array.isArray(initialData.parentTagId) ? initialData.parentTagId : [], |         parentTagId: Array.isArray(initialData.parentTagId) ? initialData.parentTagId : [], | ||||||
| @@ -75,11 +81,16 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | |||||||
|         displayOrder: Number(initialData.displayOrder ?? 0), |         displayOrder: Number(initialData.displayOrder ?? 0), | ||||||
|         icon: initialData.icon || '', |         icon: initialData.icon || '', | ||||||
|         status: initialData.status || 'Active', |         status: initialData.status || 'Active', | ||||||
|  |         createdAt: initialData.createdAt ?? null, | ||||||
|  |         createdBy: initialData.createdBy ?? null, | ||||||
|  |         updatedAt: initialData.updatedAt ?? null, | ||||||
|  |         updatedBy: initialData.updatedBy ?? null, | ||||||
|       }); |       }); | ||||||
|     } else { |     } else { | ||||||
|       setForm({ |       setForm({ | ||||||
|         _Id: '', |         _Id: '', | ||||||
|         id: '', |         id: '', | ||||||
|  |         tenantId: extractTenantId(token) || '', | ||||||
|         tagName: '', |         tagName: '', | ||||||
|         typeId: '', |         typeId: '', | ||||||
|         parentTagId: [], |         parentTagId: [], | ||||||
| @@ -87,6 +98,10 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | |||||||
|         displayOrder: 0, |         displayOrder: 0, | ||||||
|         icon: '', |         icon: '', | ||||||
|         status: 'Active', |         status: 'Active', | ||||||
|  |         createdAt: null, | ||||||
|  |         createdBy: null, | ||||||
|  |         updatedAt: null, | ||||||
|  |         updatedBy: null, | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   }, [initialData]); |   }, [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 ( |   return ( | ||||||
|     <Paper sx={{ p: 2 }}> |     <Paper sx={{ p: 2 }}> | ||||||
|       <Typography variant="subtitle1" sx={{ mb: 2 }}> |       <Typography variant="subtitle1" sx={{ mb: 2 }}> | ||||||
|         {form._Id ? 'Edit Category' : 'Add Category'} |         {form._Id ? 'Edit Category' : 'Add Category'} | ||||||
|       </Typography> |       </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 |       <TextField | ||||||
|         name="tagName" |         name="tagName" | ||||||
|         label="Name" |         label="Name" | ||||||
| @@ -239,10 +286,24 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) | |||||||
|         <MenuItem value="Inactive">Inactive</MenuItem> |         <MenuItem value="Inactive">Inactive</MenuItem> | ||||||
|       </TextField> |       </TextField> | ||||||
|  |  | ||||||
|       <Box display="flex" justifyContent="flex-end" gap={1} mt={3}> |       {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 onClick={onCancel} className="button-transparent">Cancel</Button> | ||||||
|           <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> |           <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> | ||||||
|         </Box> |         </Box> | ||||||
|  |       </Box> | ||||||
|     </Paper> |     </Paper> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ import { useEffect, useMemo, useRef, useState } from 'react'; | |||||||
| import { Box, Button, Dialog, DialogContent, DialogTitle, IconButton, Typography, | import { Box, Button, Dialog, DialogContent, DialogTitle, IconButton, Typography, | ||||||
|          ToggleButton, ToggleButtonGroup } from '@mui/material'; |          ToggleButton, ToggleButtonGroup } from '@mui/material'; | ||||||
| import { DataGrid } from '@mui/x-data-grid'; | import { DataGrid } from '@mui/x-data-grid'; | ||||||
| import EditIcon from '@mui/icons-material/Edit'; | import EditRoundedIcon from '@mui/icons-material/EditRounded'; | ||||||
| import DeleteIcon from '@mui/icons-material/Delete'; | import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; | ||||||
| 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'; | ||||||
| @@ -14,7 +14,7 @@ export default function Categories() { | |||||||
|   const api = useMemo(() => new CategoriesApi(token), [token]); |   const api = useMemo(() => new CategoriesApi(token), [token]); | ||||||
|  |  | ||||||
|   const [rows, setRows] = useState([]); |   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 [open, setOpen] = useState(false); | ||||||
|   const [editingCategory, setEditingCategory] = useState(null); |   const [editingCategory, setEditingCategory] = useState(null); | ||||||
|   const [confirmOpen, setConfirmOpen] = useState(false); |   const [confirmOpen, setConfirmOpen] = useState(false); | ||||||
| @@ -31,13 +31,8 @@ export default function Categories() { | |||||||
|   const loadData = async () => { |   const loadData = async () => { | ||||||
|     try { |     try { | ||||||
|       const data = await api.getAll(); |       const data = await api.getAll(); | ||||||
|       const safeRows = (Array.isArray(data) ? data : []) |       const list = Array.isArray(data) ? data : []; | ||||||
|         .filter(Boolean) |       setRows(list); | ||||||
|         .map((r, idx) => ({ |  | ||||||
|           ...r, |  | ||||||
|           __rid: r?._id || r?._Id || r?.id || r?.Id || `tmp-${idx}`, |  | ||||||
|         })); |  | ||||||
|       setRows(safeRows); |  | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       console.error('Failed to load categories:', e); |       console.error('Failed to load categories:', e); | ||||||
|       setRows([]); |       setRows([]); | ||||||
| @@ -110,48 +105,79 @@ export default function Categories() { | |||||||
|   }, [rows, statusFilter]); |   }, [rows, statusFilter]); | ||||||
|  |  | ||||||
|   const columns = [ |   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', |       field: 'actions', | ||||||
|       headerName: '', |       headerName: '', | ||||||
|       width: 120, |       width: 130, | ||||||
|       sortable: false, |       sortable: false, | ||||||
|       filterable: false, |       filterable: false, | ||||||
|       renderCell: (params) => { |       disableExport: true, | ||||||
|         const r = params?.row; |       renderCell: (params) => ( | ||||||
|         if (!r) return null; |         <Box display="flex" alignItems="center" justifyContent="flex-start" height="100%" gap={1}> | ||||||
|         return ( |           <IconButton | ||||||
|           <Box sx={{ display: 'flex', gap: 1 }}> |             size="small" | ||||||
|             <IconButton size="small" onClick={() => handleEditClick(params)}><EditIcon /></IconButton> |             sx={{ | ||||||
|             <IconButton size="small" color="error" onClick={() => handleDeleteClick(r)}><DeleteIcon /></IconButton> |               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> |         </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 ( |   return ( | ||||||
| @@ -177,15 +203,29 @@ export default function Categories() { | |||||||
|         </Box> |         </Box> | ||||||
|       </Box> |       </Box> | ||||||
|  |  | ||||||
|  |       <Box sx={{ width: '100%', overflowX: 'auto' }}> | ||||||
|         <DataGrid |         <DataGrid | ||||||
|           rows={filteredRows} |           rows={filteredRows} | ||||||
|           columns={columns} |           columns={columns} | ||||||
|         pageSize={10} |           initialState={{ pagination: { paginationModel: { pageSize: 10 } } }} | ||||||
|         rowsPerPageOptions={[10]} |           pageSizeOptions={[10]} | ||||||
|           autoHeight |           autoHeight | ||||||
|           disableColumnMenu |           disableColumnMenu | ||||||
|         getRowId={(r) => r?.__rid} |           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> |       <Dialog open={open} onClose={() => { setOpen(false); setEditingCategory(null); }} fullWidth> | ||||||
|         <DialogTitle>{editingCategory ? 'Edit Category' : 'Add Category'}</DialogTitle> |         <DialogTitle>{editingCategory ? 'Edit Category' : 'Add Category'}</DialogTitle> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Rodolfo Ruiz
					Rodolfo Ruiz