Compare commits

..

6 Commits

Author SHA1 Message Date
Rodolfo Ruiz
f7adaf1b18 chore: clean code, add delete 2025-09-06 20:45:56 -06:00
Rodolfo Ruiz
b3209a4019 chore: delete implementation in product collection 2025-09-06 20:34:46 -06:00
Rodolfo Ruiz
efdb48919f chore: add the logic to see in view mode the categories as well 2025-09-06 20:08:39 -06:00
Rodolfo Ruiz
6b8d5acc0d chore: add the view button 2025-09-06 20:04:38 -06:00
Rodolfo Ruiz
01a19b9144 chore: improve the ui for Products 2025-09-06 19:53:10 -06:00
Rodolfo Ruiz
74d6a8b269 chore: fix the edit popup 2025-09-06 19:48:21 -06:00
6 changed files with 312 additions and 200 deletions

View File

@@ -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();
}
} }

View File

@@ -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]);
@@ -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>
{!viewOnly && (
<Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button>
)}
</Box> </Box>
</Box> </Box>
</Paper> </Paper>

View File

@@ -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>

View File

@@ -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,16 +197,18 @@ 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"
@@ -214,141 +216,145 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
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>
<Grid item xs={12} md={6}>
<TextField <TextField
select select
label="Provider" label="Provider"
fullWidth fullWidth
value={form.providerId} value={form.providerId}
onChange={(e) => setVal('providerId', e.target.value)} onChange={(e) => setVal('providerId', e.target.value)}
disabled={viewOnly}
> >
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>} {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>}
{!loadingTags && options.providers.filter(t => t.status !== 'Inactive').map(tag => ( {!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> <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem>
))} ))}
</TextField> </TextField>
</Grid>
{/* Específicos de variante */} <Box display="flex" gap={2}>
<Grid item xs={12} md={4}> {/* Color */}
<Box flex={1}>
<TextField <TextField
select select
label="Color" label="Color"
fullWidth fullWidth
value={form.color} value={form.color}
onChange={(e) => setVal('color', e.target.value)} onChange={(e) => setVal('color', e.target.value)}
disabled={viewOnly}
> >
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>} {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>}
{!loadingTags && options.colors.filter(t => t.status !== 'Inactive').map(tag => ( {!loadingTags && options.colors.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> </Box>
{/* Line */}
<Grid item xs={12} md={4}> <Box flex={1}>
<TextField <TextField
select select
label="Line" label="Line"
fullWidth fullWidth
value={form.line} value={form.line}
onChange={(e) => setVal('line', e.target.value)} onChange={(e) => setVal('line', e.target.value)}
disabled={viewOnly}
> >
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>} {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>}
{!loadingTags && options.lines.filter(t => t.status !== 'Inactive').map(tag => ( {!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> <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem>
))} ))}
</TextField> </TextField>
</Grid> </Box>
{/* Material */}
<Grid item xs={12} md={4}> <Box flex={1}>
<TextField
select
label="Currency"
fullWidth
value={form.currency}
onChange={(e) => setVal('currency', e.target.value)}
>
{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>
</Grid>
{/* Atributos como catálogos */}
<Grid item xs={12} md={4}>
<TextField <TextField
select select
label="Material" label="Material"
fullWidth fullWidth
value={form.attributes.material} value={form.attributes.material}
onChange={(e) => setVal('attributes.material', e.target.value)} onChange={(e) => setVal('attributes.material', e.target.value)}
disabled={viewOnly}
> >
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>} {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>}
{!loadingTags && options.materials.filter(t => t.status !== 'Inactive').map(tag => ( {!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> <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem>
))} ))}
</TextField> </TextField>
</Grid> </Box>
</Box>
<Grid item xs={12} md={4}> <Box display="flex" gap={2}>
{/* Price */}
<Box flex={1}>
<TextField label="Price" type="number" fullWidth value={form.price} onChange={(e) => setVal('price', e.target.value)} disabled={viewOnly} />
</Box>
{/* Stock */}
<Box flex={1}>
<TextField label="Stock" type="number" fullWidth value={form.stock} onChange={(e) => setVal('stock', e.target.value)} disabled={viewOnly} />
</Box>
{/* Currency */}
<Box flex={1}>
<TextField
select
label="Currency"
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>
{/* Attributes */}
<TextField <TextField
select select
label="Legs" label="Legs"
fullWidth fullWidth
value={form.attributes.legs} value={form.attributes.legs}
onChange={(e) => setVal('attributes.legs', e.target.value)} onChange={(e) => setVal('attributes.legs', e.target.value)}
disabled={viewOnly}
> >
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>} {loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando</MenuItem>}
{!loadingTags && options.legs.filter(t => t.status !== 'Inactive').map(tag => ( {!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> <MenuItem key={tag._id?.$oid || tag._id || tag.id} value={tag.tagName}>{tag.tagName}</MenuItem>
))} ))}
</TextField> </TextField>
</Grid>
<Grid item xs={12} md={4}>
<TextField <TextField
select select
label="Origin" label="Origin"
fullWidth fullWidth
value={form.attributes.origin} value={form.attributes.origin}
onChange={(e) => setVal('attributes.origin', e.target.value)} onChange={(e) => setVal('attributes.origin', e.target.value)}
disabled={viewOnly}
> >
{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.origins.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>
{/* 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>
</Grid> </Box>
</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>
{!viewOnly && (
<Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> <Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button>
)}
</Box> </Box>
</Box> </Box>
); );

View File

@@ -8,6 +8,7 @@ import {
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,7 +43,7 @@ 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([]);
@@ -52,6 +53,10 @@ export default function ProductCollections() {
const [statusFilter, setStatusFilter] = useState('All'); 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({});
@@ -81,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;
@@ -157,7 +153,7 @@ export default function ProductCollections() {
setRawRows(normalized); setRawRows(normalized);
} 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);
} }
@@ -173,11 +169,29 @@ export default function ProductCollections() {
} }
}, [statusFilter, rawRows]); }, [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: 'actions', field: 'actions',
headerName: '', headerName: '',
width: 130, width: 190,
sortable: false, sortable: false,
filterable: false, filterable: false,
disableExport: true, disableExport: true,
@@ -192,10 +206,23 @@ export default function ProductCollections() {
borderRadius: 2, borderRadius: 2,
p: 1, p: 1,
}} }}
onClick={() => { setEditRow(params.row); setOpen(true); }} onClick={() => { setEditRow(params.row); setViewOnly(false); setOpen(true); }}
> >
<EditRoundedIcon fontSize="small" /> <EditRoundedIcon fontSize="small" />
</IconButton> </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 <IconButton
size="small" size="small"
sx={{ sx={{
@@ -205,7 +232,7 @@ export default function ProductCollections() {
borderRadius: 2, borderRadius: 2,
p: 1, p: 1,
}} }}
onClick={() => handleDeleteClick(params?.row)} onClick={() => { setViewOnly(false); handleDeleteClick(params?.row); }}
> >
<DeleteRoundedIcon fontSize="small" /> <DeleteRoundedIcon fontSize="small" />
</IconButton> </IconButton>
@@ -273,10 +300,16 @@ export default function ProductCollections() {
]; ];
return ( return (
<Box sx={{ height: 'calc(100vh - 64px - 64px)', display: 'flex', flexDirection: 'column', gap: 2 }}> <Box
<SectionContainer sx={{ px: 0, maxWidth: '100%!important', width: '100%' }}> sx={{
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}> <Box display="flex" alignItems="center" justifyContent="space-between" mb={2} flexWrap="wrap" gap={2}>
<Typography variant="h6">Product Collection</Typography> <Typography color='text.primary' variant="h6">Product Collection</Typography>
<Box display="flex" alignItems="center" gap={2}> <Box display="flex" alignItems="center" gap={2}>
<ToggleButtonGroup <ToggleButtonGroup
value={statusFilter} value={statusFilter}
@@ -288,7 +321,7 @@ export default function ProductCollections() {
<ToggleButton value="All">All</ToggleButton> <ToggleButton value="All">All</ToggleButton>
<ToggleButton value="Inactive">Inactive</ToggleButton> <ToggleButton value="Inactive">Inactive</ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
<Button variant="contained" className="button-gold" startIcon={<AddRoundedIcon />} onClick={() => { setEditRow(null); setOpen(true); }}> <Button variant="contained" className="button-gold" startIcon={<AddRoundedIcon />} onClick={() => { setEditRow(null); setViewOnly(false); setOpen(true); }}>
Add Product Collection Add Product Collection
</Button> </Button>
</Box> </Box>
@@ -308,35 +341,35 @@ export default function ProductCollections() {
getRowHeight={() => 'auto'} getRowHeight={() => 'auto'}
columnBuffer={0} columnBuffer={0}
sx={{ sx={{
width: '100%', height: '100%',
'& .MuiDataGrid-cell': { '& .MuiDataGrid-cell, & .MuiDataGrid-columnHeader': {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
whiteSpace: 'normal',
wordBreak: 'break-word',
minHeight: '100%',
}, },
'& .MuiDataGrid-columnHeader': { '& .MuiDataGrid-filler': {
display: 'flex', display: 'none',
alignItems: 'center',
whiteSpace: 'normal',
wordBreak: 'break-word',
},
'& .MuiDataGrid-virtualScroller': {
overflowX: 'auto',
},
'& .MuiDataGrid-main': {
minWidth: '1000px',
}, },
}} }}
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) {
@@ -349,7 +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> </Box>
); );
} }