features: services and filters implemented

This commit is contained in:
2025-09-01 19:09:48 -06:00
parent 2dc6cf9fcb
commit 24f82889e1
4 changed files with 290 additions and 182 deletions

View File

@@ -1,5 +1,6 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { Box, Button, Dialog, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material';
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';
@@ -13,6 +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 [open, setOpen] = useState(false);
const [editingCategory, setEditingCategory] = useState(null);
const [confirmOpen, setConfirmOpen] = useState(false);
@@ -29,7 +31,13 @@ export default function Categories() {
const loadData = async () => {
try {
const data = await api.getAll();
setRows(Array.isArray(data) ? data : []);
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);
} catch (e) {
console.error('Failed to load categories:', e);
setRows([]);
@@ -47,32 +55,41 @@ export default function Categories() {
setEditingCategory({
_Id: r._id || r._Id || '',
id: r.id || r.Id || '',
name: r.tagName ?? r.name ?? '',
slug: r.slug ?? '',
typeId: r.typeId ?? '',
tagName: r.tagName || r.name || '',
typeId: r.typeId || '',
parentTagId: Array.isArray(r.parentTagId) ? r.parentTagId : [],
displayOrder: Number.isFinite(r.displayOrder) ? r.displayOrder : 0,
icon: r.icon ?? '',
slug: r.slug || '',
displayOrder: Number(r.displayOrder ?? 0),
icon: r.icon || '',
status: r.status ?? 'Active',
});
setOpen(true);
};
const handleDeleteClick = (row) => {
if (!row) return;
setRowToDelete(row);
setConfirmOpen(true);
};
const pickHexId = (r) =>
[r?._id, r?._Id, r?.id, r?.Id]
.filter(Boolean)
.find((x) => typeof x === 'string' && /^[0-9a-f]{24}$/i.test(x)) || null;
const confirmDelete = async () => {
try {
if (!rowToDelete) return;
await api.changeStatus({
id: rowToDelete.id || rowToDelete.Id || rowToDelete._id || rowToDelete._Id,
status: 'Inactive',
});
const hexId = pickHexId(rowToDelete);
if (!hexId) {
alert('No se encontró _id (24-hex) para ChangeStatus en esta fila.');
return;
}
await api.changeStatus({ id: hexId, status: 'Inactive' });
await loadData();
} catch (e) {
console.error('Delete failed:', e);
alert('Delete failed. Revisa la consola para más detalles.');
} finally {
setConfirmOpen(false);
setRowToDelete(null);
@@ -85,41 +102,89 @@ export default function Categories() {
setEditingCategory(null);
};
// --- FILTRO DE ESTADO ---
const filteredRows = useMemo(() => {
if (statusFilter === 'All') return rows;
const want = String(statusFilter).toLowerCase();
return rows.filter((r) => String(r?.status ?? 'Active').toLowerCase() === want);
}, [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: 220 },
{ field: 'displayOrder', headerName: 'Display', width: 120, valueGetter: (p) => p.row?.displayOrder ?? 0 },
{ field: 'status', headerName: 'Status', width: 140, valueGetter: (p) => p.row?.status ?? 'Active' },
{
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,
sortable: false,
filterable: false,
renderCell: (params) => (
<Box sx={{ display: 'flex', gap: 1 }}>
<IconButton size="small" onClick={() => handleEditClick(params)}><EditIcon /></IconButton>
<IconButton size="small" color="error" onClick={() => handleDeleteClick(params.row)}><DeleteIcon /></IconButton>
</Box>
),
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>
);
},
},
];
return (
<Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, gap: 2, flexWrap: 'wrap' }}>
<Typography variant="h6">Categories</Typography>
<Button variant="contained" onClick={handleAddClick} className="button-gold">Add Category</Button>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<ToggleButtonGroup
value={statusFilter}
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" onClick={handleAddClick} className="button-gold">
Add Category
</Button>
</Box>
</Box>
<DataGrid
rows={rows}
rows={filteredRows}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
autoHeight
disableColumnMenu
getRowId={(r) => r._id || r._Id || r.id || r.Id}
getRowId={(r) => r?.__rid}
/>
<Dialog open={open} onClose={() => { setOpen(false); setEditingCategory(null); }} fullWidth>