diff --git a/src/api/CategoriesApi.js b/src/api/CategoriesApi.js index af98bcc..5a2310d 100644 --- a/src/api/CategoriesApi.js +++ b/src/api/CategoriesApi.js @@ -1,8 +1,7 @@ // src/api/CategoriesApi.js export default class CategoriesApi { constructor(token) { - this.tagUrl = 'https://inventory-bff.dream-views.com/api/v1/Tag'; // <— singular - this.tagTypeUrl = 'https://inventory-bff.dream-views.com/api/v1/TagType'; + this.root = 'https://inventory-bff.dream-views.com/api/v1'; this.token = token; } @@ -14,9 +13,9 @@ export default class CategoriesApi { }; } - // TAGS + // ---- Tag ---- async getAll() { - const res = await fetch(`${this.tagUrl}/GetAll`, { + const res = await fetch(`${this.root}/Tag/GetAll`, { method: 'GET', headers: this.headers(false), }); @@ -24,8 +23,8 @@ export default class CategoriesApi { return res.json(); } - async create(payload) { // CreateTagRequest - const res = await fetch(`${this.tagUrl}/Create`, { + async create(payload) { + const res = await fetch(`${this.root}/Tag/Create`, { method: 'POST', headers: this.headers(), body: JSON.stringify(payload), @@ -34,8 +33,8 @@ export default class CategoriesApi { return res.json(); } - async update(payload) { // UpdateTagRequest - const res = await fetch(`${this.tagUrl}/Update`, { + async update(payload) { + const res = await fetch(`${this.root}/Tag/Update`, { method: 'PUT', headers: this.headers(), body: JSON.stringify(payload), @@ -44,14 +43,15 @@ export default class CategoriesApi { return res.json(); } - async changeStatus(payload) { // { id, status } - const res = await fetch(`${this.tagUrl}/ChangeStatus`, { + // PATCH ChangeStatus: body { id, status } + async changeStatus({ id, status }) { + const res = await fetch(`${this.root}/Tag/ChangeStatus`, { method: 'PATCH', headers: this.headers(), - body: JSON.stringify(payload), + body: JSON.stringify({ id, status }), }); if (!res.ok) throw new Error(`ChangeStatus error ${res.status}: ${await res.text()}`); - return res.json(); + return res.json?.() ?? null; } async delete(payload) { @@ -63,14 +63,15 @@ export default class CategoriesApi { if (!res.ok) throw new Error(`Delete error ${res.status}: ${await res.text()}`); return res.json(); } - - // TAG TYPES (para updateField('typeId', e.target.value)} - > - {typeOptions.map((opt) => ( - {opt.label} - ))} - - - - - Parent Categories - - - updateField('displayOrder', e.target.value)} + value={form.displayOrder} + onChange={handleChange} fullWidth sx={{ mb: 2 }} /> updateField('icon', e.target.value)} + label="Icon URL" + value={form.icon} + onChange={handleChange} fullWidth + sx={{ mb: 2 }} + required /> - {category._Id && ( - - Status - - - )} + + Active + Inactive + diff --git a/src/private/categories/Categories.jsx b/src/private/categories/Categories.jsx index e6db26f..5f023c5 100644 --- a/src/private/categories/Categories.jsx +++ b/src/private/categories/Categories.jsx @@ -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) => ( - - handleEditClick(params)}> - handleDeleteClick(params.row)}> - - ), + renderCell: (params) => { + const r = params?.row; + if (!r) return null; + return ( + + handleEditClick(params)}> + handleDeleteClick(r)}> + + ); + }, }, ]; return ( - + Categories - + + + v && setStatusFilter(v)} + size="small" + > + Active + All + Inactive + + + + r._id || r._Id || r.id || r.Id} + getRowId={(r) => r?.__rid} /> { setOpen(false); setEditingCategory(null); }} fullWidth> diff --git a/src/private/fornitures/FurnitureVariantManagement.jsx b/src/private/fornitures/FurnitureVariantManagement.jsx index e2735b7..d11321f 100644 --- a/src/private/fornitures/FurnitureVariantManagement.jsx +++ b/src/private/fornitures/FurnitureVariantManagement.jsx @@ -1,9 +1,9 @@ import SectionContainer from '../../components/SectionContainer'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useMemo } from 'react'; import { DataGrid } from '@mui/x-data-grid'; import { Typography, Button, Dialog, DialogTitle, DialogContent, - IconButton, Box + IconButton, Box, ToggleButton, ToggleButtonGroup } from '@mui/material'; import EditRoundedIcon from '@mui/icons-material/EditRounded'; import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; @@ -65,6 +65,7 @@ export default function FurnitureVariantManagement() { const apiRef = useRef(null); const [rows, setRows] = useState([]); + const [statusFilter, setStatusFilter] = useState('Active'); // <- por defecto Active const [open, setOpen] = useState(false); const [editingData, setEditingData] = useState(null); const [confirmOpen, setConfirmOpen] = useState(false); @@ -129,7 +130,7 @@ export default function FurnitureVariantManagement() { const handleConfirmDelete = async () => { try { if (!apiRef.current || !(rowToDelete?._id || rowToDelete?._Id)) throw new Error('Missing API or id'); - // If your inventory BFF uses soft delete via Update (status=Inactive), do this: + // Soft-delete vía Update (status=Inactive) const payload = { id: rowToDelete.id || rowToDelete.Id || '', _Id: rowToDelete._id || rowToDelete._Id, @@ -149,7 +150,6 @@ export default function FurnitureVariantManagement() { }, status: 'Inactive', }; - // Prefer update soft-delete; if you truly have DELETE, switch to apiRef.current.deleteVariant({ _Id: ... }) await apiRef.current.updateVariant(payload); await loadData(); } catch (e) { @@ -160,6 +160,13 @@ export default function FurnitureVariantManagement() { } }; + // --- 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: 'actions', @@ -229,9 +236,23 @@ export default function FurnitureVariantManagement() { - + {/* Toolbar de filtro */} + + v && setStatusFilter(v)} + size="small" + > + Active + All + Inactive + + + + ); -} \ No newline at end of file +}