Compare commits
4 Commits
01a19b9144
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7adaf1b18 | ||
|
|
b3209a4019 | ||
|
|
efdb48919f | ||
|
|
6b8d5acc0d |
@@ -52,4 +52,15 @@ export default class ProductsApi {
|
||||
if (!res.ok) throw new Error(`Delete error ${res.status}: ${await res.text()}`);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ function formatDateSafe(value) {
|
||||
}).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 token = user?.thalosToken || localStorage.getItem('thalosToken');
|
||||
const api = useMemo(() => new CategoriesApi(token), [token]);
|
||||
@@ -52,14 +52,14 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel, ma
|
||||
const [types, setTypes] = useState([]);
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
|
||||
const tagLabelById = useMemo(() => {
|
||||
const tagLabelById = useMemo(() => {
|
||||
const map = {};
|
||||
for (const t of allTags) {
|
||||
const key = t._id;
|
||||
map[key] = t.tagName || t.name || key;
|
||||
}
|
||||
return map;
|
||||
}, [allTags]);
|
||||
}, [allTags]);
|
||||
|
||||
const [form, setForm] = useState({
|
||||
_id: '',
|
||||
@@ -248,6 +248,7 @@ const tagLabelById = useMemo(() => {
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
InputProps={{ readOnly: true }}
|
||||
disabled={viewOnly}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -259,6 +260,7 @@ const tagLabelById = useMemo(() => {
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
required
|
||||
disabled={viewOnly}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@@ -270,6 +272,7 @@ const tagLabelById = useMemo(() => {
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
required
|
||||
disabled={viewOnly}
|
||||
>
|
||||
{types.map((t) => {
|
||||
const value = t._id;
|
||||
@@ -304,6 +307,7 @@ const tagLabelById = useMemo(() => {
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
disabled={viewOnly}
|
||||
>
|
||||
{allTags.map((t) => {
|
||||
const value = t._id;
|
||||
@@ -323,6 +327,7 @@ const tagLabelById = useMemo(() => {
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
disabled={viewOnly}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@@ -333,6 +338,7 @@ const tagLabelById = useMemo(() => {
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
disabled={viewOnly}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@@ -343,6 +349,7 @@ const tagLabelById = useMemo(() => {
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
required
|
||||
disabled={viewOnly}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@@ -353,6 +360,7 @@ const tagLabelById = useMemo(() => {
|
||||
select
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
disabled={viewOnly}
|
||||
>
|
||||
<MenuItem value="Active">Active</MenuItem>
|
||||
<MenuItem value="Inactive">Inactive</MenuItem>
|
||||
@@ -368,12 +376,14 @@ const tagLabelById = useMemo(() => {
|
||||
) : null}
|
||||
|
||||
<Box display="flex" justifyContent="space-between" gap={1} mt={3}>
|
||||
{form._id ? (
|
||||
{form._id && !viewOnly ? (
|
||||
<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">{viewOnly ? 'Close' : 'Cancel'}</Button>
|
||||
{!viewOnly && (
|
||||
<Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import EditRoundedIcon from '@mui/icons-material/EditRounded';
|
||||
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
|
||||
import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded';
|
||||
import AddOrEditCategoryForm from './AddOrEditCategoryForm';
|
||||
import CategoriesApi from '../../../api/CategoriesApi';
|
||||
import { useAuth } from '../../../context/AuthContext';
|
||||
@@ -22,6 +23,7 @@ export default function Categories() {
|
||||
const [editingCategory, setEditingCategory] = useState(null);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [rowToDelete, setRowToDelete] = useState(null);
|
||||
const [viewOnly, setViewOnly] = useState(false);
|
||||
const hasLoaded = useRef(false);
|
||||
|
||||
const pageSize = 100; // Número de filas por página
|
||||
@@ -68,11 +70,13 @@ export default function Categories() {
|
||||
};
|
||||
|
||||
const handleAddClick = () => {
|
||||
setViewOnly(false);
|
||||
setEditingCategory(null);
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleEditClick = (params) => {
|
||||
setViewOnly(false);
|
||||
const r = params?.row;
|
||||
if (!r) return;
|
||||
setEditingCategory({
|
||||
@@ -145,7 +149,7 @@ export default function Categories() {
|
||||
{
|
||||
field: 'actions',
|
||||
headerName: '',
|
||||
width: 130,
|
||||
width: 150,
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
disableExport: true,
|
||||
@@ -164,6 +168,44 @@ export default function Categories() {
|
||||
>
|
||||
<EditRoundedIcon fontSize="small" />
|
||||
</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
|
||||
size="small"
|
||||
sx={{
|
||||
@@ -319,6 +361,7 @@ export default function Categories() {
|
||||
initialMaterialNames={editingCategory?.materialNames || []}
|
||||
onAdd={handleFormDone}
|
||||
onCancel={() => { setOpen(false); setEditingCategory(null); }}
|
||||
viewOnly={viewOnly}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -327,8 +370,8 @@ export default function Categories() {
|
||||
<DialogTitle>Delete Category</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ display: 'flex', gap: 1, mt: 2, mb: 1 }}>
|
||||
<Button onClick={() => setConfirmOpen(false)}>Cancel</Button>
|
||||
<Button color="error" variant="contained" onClick={confirmDelete}>Delete</Button>
|
||||
<Button onClick={() => setConfirmOpen(false)} className='button-transparent'>Cancel</Button>
|
||||
<Button color="error" variant="contained" onClick={confirmDelete} className="button-gold">Delete</Button>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -19,7 +19,7 @@ const TYPE_NAMES = {
|
||||
origin: 'Origin',
|
||||
};
|
||||
|
||||
export default function AddOrEditProductCollectionForm({ initialData, onAdd, onCancel }) {
|
||||
export default function AddOrEditProductCollectionForm({ initialData, onAdd, onCancel, viewOnly = false }) {
|
||||
const { user } = useAuth();
|
||||
const token = user?.thalosToken || localStorage.getItem('thalosToken');
|
||||
|
||||
@@ -205,6 +205,7 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
value={form.name}
|
||||
onChange={(e) => setVal('name', e.target.value)}
|
||||
sx={{ mt: 1 }}
|
||||
disabled={viewOnly}
|
||||
/>
|
||||
|
||||
{/* Category / Provider */}
|
||||
@@ -215,6 +216,7 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
value={form.categoryId}
|
||||
onChange={(e) => setVal('categoryId', e.target.value)}
|
||||
helperText="Se envía el tagName por ahora"
|
||||
disabled={viewOnly}
|
||||
>
|
||||
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>}
|
||||
{!loadingTags && options.categories.filter(t => t.status !== 'Inactive').map(tag => (
|
||||
@@ -227,6 +229,7 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
fullWidth
|
||||
value={form.providerId}
|
||||
onChange={(e) => setVal('providerId', e.target.value)}
|
||||
disabled={viewOnly}
|
||||
>
|
||||
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>}
|
||||
{!loadingTags && options.providers.filter(t => t.status !== 'Inactive').map(tag => (
|
||||
@@ -243,6 +246,7 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
fullWidth
|
||||
value={form.color}
|
||||
onChange={(e) => setVal('color', e.target.value)}
|
||||
disabled={viewOnly}
|
||||
>
|
||||
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>}
|
||||
{!loadingTags && options.colors.filter(t => t.status !== 'Inactive').map(tag => (
|
||||
@@ -258,6 +262,7 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
fullWidth
|
||||
value={form.line}
|
||||
onChange={(e) => setVal('line', e.target.value)}
|
||||
disabled={viewOnly}
|
||||
>
|
||||
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>}
|
||||
{!loadingTags && options.lines.filter(t => t.status !== 'Inactive').map(tag => (
|
||||
@@ -273,6 +278,7 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
fullWidth
|
||||
value={form.attributes.material}
|
||||
onChange={(e) => setVal('attributes.material', e.target.value)}
|
||||
disabled={viewOnly}
|
||||
>
|
||||
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>}
|
||||
{!loadingTags && options.materials.filter(t => t.status !== 'Inactive').map(tag => (
|
||||
@@ -285,11 +291,11 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
<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)} />
|
||||
<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)} />
|
||||
<TextField label="Stock" type="number" fullWidth value={form.stock} onChange={(e) => setVal('stock', e.target.value)} disabled={viewOnly} />
|
||||
</Box>
|
||||
{/* Currency */}
|
||||
<Box flex={1}>
|
||||
@@ -299,6 +305,7 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
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 => (
|
||||
@@ -315,6 +322,7 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
fullWidth
|
||||
value={form.attributes.legs}
|
||||
onChange={(e) => setVal('attributes.legs', e.target.value)}
|
||||
disabled={viewOnly}
|
||||
>
|
||||
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>}
|
||||
{!loadingTags && options.legs.filter(t => t.status !== 'Inactive').map(tag => (
|
||||
@@ -327,6 +335,7 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
fullWidth
|
||||
value={form.attributes.origin}
|
||||
onChange={(e) => setVal('attributes.origin', e.target.value)}
|
||||
disabled={viewOnly}
|
||||
>
|
||||
{loadingTags && <MenuItem value="" disabled><CircularProgress size={16} /> Cargando…</MenuItem>}
|
||||
{!loadingTags && options.origins.filter(t => t.status !== 'Inactive').map(tag => (
|
||||
@@ -335,15 +344,17 @@ export default function AddOrEditProductCollectionForm({ initialData, onAdd, onC
|
||||
</TextField>
|
||||
|
||||
{/* Status */}
|
||||
<TextField select label="Status" fullWidth value={form.status} onChange={(e) => setVal('status', e.target.value)}>
|
||||
<TextField select label="Status" fullWidth value={form.status} onChange={(e) => setVal('status', e.target.value)} disabled={viewOnly}>
|
||||
<MenuItem value="Active">Active</MenuItem>
|
||||
<MenuItem value="Inactive">Inactive</MenuItem>
|
||||
</TextField>
|
||||
</Box>
|
||||
|
||||
<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>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import EditRoundedIcon from '@mui/icons-material/EditRounded';
|
||||
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
|
||||
import AddRoundedIcon from '@mui/icons-material/AddRounded';
|
||||
import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded';
|
||||
import AddOrEditProductCollectionForm from './AddOrEditProductCollectionForm';
|
||||
import FurnitureVariantApi from '../../../api/ProductsApi';
|
||||
import CategoriesApi from '../../../api/CategoriesApi';
|
||||
@@ -42,7 +43,7 @@ export default function ProductCollections() {
|
||||
const tagTypeApi = useMemo(() => new TagTypeApi(token), [token]);
|
||||
const categoriesApi = useMemo(() => new CategoriesApi(token), [token]);
|
||||
|
||||
const toast = useApiToast();
|
||||
const { handleError } = useApiToast();
|
||||
|
||||
const [rows, setRows] = useState([]);
|
||||
const [rawRows, setRawRows] = useState([]);
|
||||
@@ -52,6 +53,10 @@ export default function ProductCollections() {
|
||||
|
||||
const [statusFilter, setStatusFilter] = useState('All');
|
||||
|
||||
const [viewOnly, setViewOnly] = useState(false);
|
||||
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
|
||||
// Tags
|
||||
const [loadingTags, setLoadingTags] = useState(true);
|
||||
const [typeMap, setTypeMap] = useState({});
|
||||
@@ -148,7 +153,7 @@ export default function ProductCollections() {
|
||||
setRawRows(normalized);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(err?.message || 'Error loading variants');
|
||||
handleError(err, 'Error loading product collections');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -164,11 +169,29 @@ export default function ProductCollections() {
|
||||
}
|
||||
}, [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 = [
|
||||
{
|
||||
field: 'actions',
|
||||
headerName: '',
|
||||
width: 130,
|
||||
width: 190,
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
disableExport: true,
|
||||
@@ -183,10 +206,23 @@ export default function ProductCollections() {
|
||||
borderRadius: 2,
|
||||
p: 1,
|
||||
}}
|
||||
onClick={() => { setEditRow(params.row); setOpen(true); }}
|
||||
onClick={() => { setEditRow(params.row); setViewOnly(false); setOpen(true); }}
|
||||
>
|
||||
<EditRoundedIcon fontSize="small" />
|
||||
</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
|
||||
size="small"
|
||||
sx={{
|
||||
@@ -196,7 +232,7 @@ export default function ProductCollections() {
|
||||
borderRadius: 2,
|
||||
p: 1,
|
||||
}}
|
||||
onClick={() => handleDeleteClick(params?.row)}
|
||||
onClick={() => { setViewOnly(false); handleDeleteClick(params?.row); }}
|
||||
>
|
||||
<DeleteRoundedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
@@ -285,7 +321,7 @@ export default function ProductCollections() {
|
||||
<ToggleButton value="All">All</ToggleButton>
|
||||
<ToggleButton value="Inactive">Inactive</ToggleButton>
|
||||
</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
|
||||
</Button>
|
||||
</Box>
|
||||
@@ -323,10 +359,17 @@ export default function ProductCollections() {
|
||||
</Box>
|
||||
|
||||
<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>
|
||||
<AddOrEditProductCollectionForm
|
||||
initialData={editRow}
|
||||
viewOnly={viewOnly}
|
||||
onAdd={(saved) => {
|
||||
setOpen(false);
|
||||
if (editRow) {
|
||||
@@ -339,6 +382,16 @@ export default function ProductCollections() {
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user