Compare commits
2 Commits
fead820091
...
aa62b06c23
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa62b06c23 | ||
|
|
d699af9d75 |
@@ -1,4 +1,3 @@
|
||||
// src/private/categories/AddOrEditCategoryForm.jsx
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Box, Button, Paper, TextField, Typography, MenuItem, Chip } from '@mui/material';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
@@ -18,16 +17,15 @@ function extractTenantId(token) {
|
||||
const payload = jwtDecode(token);
|
||||
const t = payload?.tenant;
|
||||
if (Array.isArray(t)) {
|
||||
// prefer a 24-hex string if present
|
||||
const hex = t.find(x => typeof x === 'string' && /^[0-9a-f]{24}$/i.test(x));
|
||||
return hex || (typeof t[0] === 'string' ? t[0] : '');
|
||||
}
|
||||
if (typeof t === 'string') return t;
|
||||
} catch {}
|
||||
} catch { }
|
||||
return '';
|
||||
}
|
||||
|
||||
export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) {
|
||||
export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel, materials: materialsProp = [], initialMaterialNames = [] }) {
|
||||
const { user } = useAuth();
|
||||
const token = user?.thalosToken || localStorage.getItem('thalosToken');
|
||||
const api = useMemo(() => new CategoriesApi(token), [token]);
|
||||
@@ -35,6 +33,15 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel })
|
||||
const [types, setTypes] = useState([]);
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
|
||||
const tagLabelById = useMemo(() => {
|
||||
const map = {};
|
||||
for (const t of allTags) {
|
||||
const key = t._id || t.id;
|
||||
map[key] = t.tagName || t.name || key;
|
||||
}
|
||||
return map;
|
||||
}, [allTags]);
|
||||
|
||||
const [form, setForm] = useState({
|
||||
_Id: '',
|
||||
id: '',
|
||||
@@ -56,15 +63,58 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel })
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const [t, tags] = await Promise.all([api.getAllTypes(), api.getAll()]);
|
||||
setTypes(Array.isArray(t) ? t : []);
|
||||
// Always try to load all tags (for materials, lookups, etc.)
|
||||
const tags = typeof api.getAll === 'function' ? await api.getAll() : [];
|
||||
|
||||
// Try multiple method names for types; if none exist, derive from tags
|
||||
let typesResp = [];
|
||||
if (typeof api.getAllTypes === 'function') {
|
||||
typesResp = await api.getAllTypes();
|
||||
} else if (typeof api.getTypes === 'function') {
|
||||
typesResp = await api.getTypes();
|
||||
} else if (Array.isArray(tags)) {
|
||||
// Derive a minimal "types" list from existing tag.typeId values
|
||||
const uniqueTypeIds = [...new Set(tags.map(t => t?.typeId).filter(Boolean))];
|
||||
typesResp = uniqueTypeIds.map(id => ({ id, typeName: id, level: null }));
|
||||
console.warn('CategoriesApi has no getAllTypes/getTypes; derived types from tag.typeId.');
|
||||
}
|
||||
|
||||
setTypes(Array.isArray(typesResp) ? typesResp : []);
|
||||
setAllTags(Array.isArray(tags) ? tags : []);
|
||||
} catch (e) {
|
||||
console.error('Failed to load tag types or tags', e);
|
||||
setTypes([]);
|
||||
setAllTags([]);
|
||||
}
|
||||
})();
|
||||
}, [api]);
|
||||
|
||||
// When editing: if we received material names from the grid, map them to IDs once allTags are loaded.
|
||||
useEffect(() => {
|
||||
if (!Array.isArray(initialMaterialNames) || initialMaterialNames.length === 0) return;
|
||||
// If parentTagId already has values (ids), do not override.
|
||||
if (Array.isArray(form.parentTagId) && form.parentTagId.length > 0) return;
|
||||
if (!Array.isArray(allTags) || allTags.length === 0) return;
|
||||
|
||||
// Build a case-insensitive name -> id map
|
||||
const nameToId = new Map(
|
||||
allTags.map(t => {
|
||||
const id = t._id || t.id;
|
||||
const label = (t.tagName || t.name || '').toLowerCase();
|
||||
return [label, id];
|
||||
})
|
||||
);
|
||||
|
||||
const ids = initialMaterialNames
|
||||
.map(n => (typeof n === 'string' ? n.toLowerCase() : ''))
|
||||
.map(lower => nameToId.get(lower))
|
||||
.filter(Boolean);
|
||||
|
||||
if (ids.length > 0) {
|
||||
setForm(prev => ({ ...prev, parentTagId: ids }));
|
||||
}
|
||||
}, [initialMaterialNames, allTags]);
|
||||
|
||||
// set inicial
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
@@ -106,6 +156,9 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel })
|
||||
}
|
||||
}, [initialData]);
|
||||
|
||||
const isEdit = Boolean(form._Id || form.id);
|
||||
const isAdd = !isEdit;
|
||||
|
||||
const setVal = (name, value) => setForm(p => ({ ...p, [name]: value }));
|
||||
|
||||
const handleChange = (e) => {
|
||||
@@ -180,15 +233,7 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel })
|
||||
{form._Id ? 'Edit Category' : 'Add Category'}
|
||||
</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) */}
|
||||
{isAdd && (
|
||||
<TextField
|
||||
name="tenantId"
|
||||
label="Tenant Id"
|
||||
@@ -197,6 +242,7 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel })
|
||||
sx={{ mb: 2 }}
|
||||
InputProps={{ readOnly: true }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
name="tagName"
|
||||
@@ -227,25 +273,35 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel })
|
||||
|
||||
<TextField
|
||||
name="parentTagId"
|
||||
label="Parent tags"
|
||||
label="Material"
|
||||
value={form.parentTagId}
|
||||
onChange={(e) => {
|
||||
// For MUI Select multiple, e.target.value is an array of selected IDs
|
||||
const val = e.target.value;
|
||||
setVal('parentTagId', typeof val === 'string' ? val.split(',').map(s => s.trim()).filter(Boolean) : val);
|
||||
setVal('parentTagId', Array.isArray(val) ? val : []);
|
||||
}}
|
||||
select
|
||||
SelectProps={{ multiple: true, renderValue: (selected) => (
|
||||
SelectProps={{
|
||||
multiple: true,
|
||||
renderValue: (selected) => (
|
||||
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
|
||||
{selected.map(v => <Chip key={v} label={v} size="small" />)}
|
||||
{selected.map((id) => (
|
||||
<Chip key={id} label={tagLabelById[id] || id} size="small" />
|
||||
))}
|
||||
</Box>
|
||||
)}}
|
||||
),
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
{allTags.map(t => {
|
||||
{allTags.map((t) => {
|
||||
const value = t._id || t.id;
|
||||
const label = t.tagName || t.name || value;
|
||||
return <MenuItem key={value} value={value}>{label}</MenuItem>;
|
||||
return (
|
||||
<MenuItem key={value} value={value}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</TextField>
|
||||
|
||||
@@ -301,9 +357,9 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel })
|
||||
) : null}
|
||||
|
||||
<Box display="flex" justifyContent="space-between" gap={1} mt={3}>
|
||||
{ (form._Id || form.id) ? (
|
||||
{(form._Id || form.id) ? (
|
||||
<Button color="error" onClick={handleDelete}>Delete</Button>
|
||||
) : <span /> }
|
||||
) : <span />}
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button onClick={onCancel} className="button-transparent">Cancel</Button>
|
||||
<Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Box, Button, Dialog, DialogContent, DialogTitle, IconButton, Typography,
|
||||
ToggleButton, ToggleButtonGroup } from '@mui/material';
|
||||
import {
|
||||
Box, Button, Dialog, DialogContent, DialogTitle, IconButton, Typography,
|
||||
ToggleButton, ToggleButtonGroup
|
||||
} from '@mui/material';
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import EditRoundedIcon from '@mui/icons-material/EditRounded';
|
||||
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
|
||||
@@ -14,6 +16,7 @@ export default function Categories() {
|
||||
const api = useMemo(() => new CategoriesApi(token), [token]);
|
||||
|
||||
const [rows, setRows] = useState([]);
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
const [statusFilter, setStatusFilter] = useState('All'); // <- por defecto All
|
||||
const [open, setOpen] = useState(false);
|
||||
const [editingCategory, setEditingCategory] = useState(null);
|
||||
@@ -35,6 +38,8 @@ export default function Categories() {
|
||||
const data = await api.getAll();
|
||||
const list = Array.isArray(data) ? data : [];
|
||||
|
||||
setAllTags(list);
|
||||
|
||||
// Build a map of parentId -> array of child tagNames
|
||||
const parentToChildren = {};
|
||||
for (const item of list) {
|
||||
@@ -76,6 +81,9 @@ export default function Categories() {
|
||||
displayOrder: Number(r.displayOrder ?? 0),
|
||||
icon: r.icon || '',
|
||||
status: r.status ?? 'Active',
|
||||
materialNames: typeof r.material === 'string'
|
||||
? r.material.split(',').map(s => s.trim()).filter(Boolean)
|
||||
: Array.isArray(r.material) ? r.material : [],
|
||||
});
|
||||
setOpen(true);
|
||||
};
|
||||
@@ -164,10 +172,8 @@ export default function Categories() {
|
||||
},
|
||||
{ field: 'tagName', headerName: 'Name', flex: 1.2, minWidth: 180 },
|
||||
{ field: 'slug', headerName: 'Slug', flex: 1.0, minWidth: 160 },
|
||||
{ field: 'icon', headerName: 'Icon', flex: 0.7, minWidth: 120 },
|
||||
// New computed column
|
||||
{ field: 'icon', headerName: 'Icon', flex: 0.7, minWidth: 250 },
|
||||
{ field: 'material', headerName: 'Material', flex: 1.2, minWidth: 200 },
|
||||
// Hidden audit columns
|
||||
{
|
||||
field: 'createdAt',
|
||||
headerName: 'Created Date',
|
||||
@@ -229,7 +235,17 @@ export default function Categories() {
|
||||
<DataGrid
|
||||
rows={filteredRows}
|
||||
columns={columns}
|
||||
initialState={{ pagination: { paginationModel: { pageSize: pageSize } } }}
|
||||
initialState={{
|
||||
pagination: { paginationModel: { pageSize } },
|
||||
columns: {
|
||||
columnVisibilityModel: {
|
||||
createdAt: false,
|
||||
createdBy: false,
|
||||
updatedAt: false,
|
||||
updatedBy: false,
|
||||
},
|
||||
},
|
||||
}}
|
||||
pageSizeOptions={[pageSize]}
|
||||
disableColumnMenu
|
||||
getRowId={(r) => r?._id || r?.id}
|
||||
@@ -256,6 +272,8 @@ export default function Categories() {
|
||||
<DialogContent>
|
||||
<AddOrEditCategoryForm
|
||||
initialData={editingCategory}
|
||||
allTags={allTags}
|
||||
initialMaterialNames={editingCategory?.materialNames || []}
|
||||
onAdd={handleFormDone}
|
||||
onCancel={() => { setOpen(false); setEditingCategory(null); }}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user