Compare commits

..

3 Commits

Author SHA1 Message Date
Rodolfo Ruiz
49dead566c chore: update, delete and create 2025-09-05 19:17:17 -06:00
Rodolfo Ruiz
2fa6b95012 chore: create items 2025-09-05 18:50:11 -06:00
Rodolfo Ruiz
f5acde78de chore: show material in the grid 2025-09-05 18:06:32 -06:00
2 changed files with 80 additions and 43 deletions

View File

@@ -55,14 +55,14 @@ export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel, ma
const tagLabelById = useMemo(() => { const tagLabelById = useMemo(() => {
const map = {}; const map = {};
for (const t of allTags) { for (const t of allTags) {
const key = t._id || t.id; const key = t._id;
map[key] = t.tagName || t.name || key; map[key] = t.tagName || t.name || key;
} }
return map; return map;
}, [allTags]); }, [allTags]);
const [form, setForm] = useState({ const [form, setForm] = useState({
_Id: '', _id: '',
id: '', id: '',
tenantId: '', tenantId: '',
tagName: '', tagName: '',
@@ -107,9 +107,9 @@ const tagLabelById = useMemo(() => {
// Build a case-insensitive name -> id map // Build a case-insensitive name -> id map
const nameToId = new Map( const nameToId = new Map(
allTags.map(t => { allTags.map(t => {
const id = t._id || t.id; const _id = t._id;
const label = (t.tagName || t.name || '').toLowerCase(); const label = (t.tagName || t.name || '').toLowerCase();
return [label, id]; return [label, _id];
}) })
); );
@@ -126,11 +126,9 @@ const tagLabelById = useMemo(() => {
// set inicial // set inicial
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
const _Id = initialData._id || initialData._Id || '';
const id = initialData.id || initialData.Id || _Id || '';
setForm({ setForm({
_Id, _id: initialData._id,
id, id: initialData.id,
tenantId: initialData.tenantId || extractTenantId(token) || '', tenantId: initialData.tenantId || extractTenantId(token) || '',
tagName: initialData.tagName || initialData.name || '', tagName: initialData.tagName || initialData.name || '',
typeId: initialData.typeId || '', typeId: initialData.typeId || '',
@@ -146,7 +144,7 @@ const tagLabelById = useMemo(() => {
}); });
} else { } else {
setForm({ setForm({
_Id: '', _id: '',
id: '', id: '',
tenantId: extractTenantId(token) || '', tenantId: extractTenantId(token) || '',
tagName: '', tagName: '',
@@ -164,7 +162,7 @@ const tagLabelById = useMemo(() => {
} }
}, [initialData]); }, [initialData]);
const isEdit = Boolean(form._Id || form.id); const isEdit = Boolean(form._id);
const isAdd = !isEdit; const isAdd = !isEdit;
const setVal = (name, value) => setForm(p => ({ ...p, [name]: value })); const setVal = (name, value) => setForm(p => ({ ...p, [name]: value }));
@@ -172,7 +170,7 @@ const tagLabelById = useMemo(() => {
const handleChange = (e) => { const handleChange = (e) => {
const { name, value } = e.target; const { name, value } = e.target;
setVal(name, value); setVal(name, value);
if (name === 'tagName' && !form._Id) { if (name === 'tagName' && !form._id) {
setVal('slug', slugify(value)); setVal('slug', slugify(value));
} }
}; };
@@ -187,6 +185,7 @@ const tagLabelById = useMemo(() => {
if (!tenantId) throw new Error('TenantId not found in token'); if (!tenantId) throw new Error('TenantId not found in token');
const base = { const base = {
id: form.id?.trim() || undefined,
tagName: form.tagName.trim(), tagName: form.tagName.trim(),
typeId: form.typeId, typeId: form.typeId,
parentTagId: form.parentTagId, parentTagId: form.parentTagId,
@@ -197,15 +196,16 @@ const tagLabelById = useMemo(() => {
tenantId, // requerido por backend (400 si falta) tenantId, // requerido por backend (400 si falta)
}; };
if (form._Id) { if (form._id) {
// UPDATE const idForUpdate = Boolean(form._id) ? String(form._id) : null;
if (!idForUpdate) throw new Error('Missing _id for update');
const payload = { const payload = {
id: form.id || form._Id, // backend acepta GUID; si no hay, mandamos _id _id: idForUpdate,
...base, ...base,
}; };
console.log('[CategoryForm] SUBMIT (edit) with _id:', idForUpdate, 'payload:', payload);
await api.update(payload); await api.update(payload);
} else { } else {
// CREATE
await api.create(base); await api.create(base);
} }
@@ -221,10 +221,9 @@ const tagLabelById = useMemo(() => {
const handleDelete = async () => { const handleDelete = async () => {
try { try {
// Try to use Mongo _Id (24-hex); if not present, fall back to GUID `id`. const idToUse = form._id;
const hex = typeof form._Id === 'string' && /^[0-9a-f]{24}$/i.test(form._Id) ? form._Id : null; if (!idToUse) throw new Error('Missing _id to delete');
const idToUse = hex || form.id; console.debug('[CategoryForm] DELETE with _id:', idToUse);
if (!idToUse) throw new Error('Missing id to delete');
await api.changeStatus({ id: idToUse, status: 'Inactive' }); await api.changeStatus({ id: idToUse, status: 'Inactive' });
if (onAdd) { if (onAdd) {
await onAdd(); await onAdd();
@@ -238,7 +237,7 @@ const tagLabelById = useMemo(() => {
return ( return (
<Paper sx={{ p: 2 }}> <Paper sx={{ p: 2 }}>
<Typography variant="subtitle1" sx={{ mb: 2 }}> <Typography variant="subtitle1" sx={{ mb: 2 }}>
{form._Id ? 'Edit Category' : 'Add Category'} {form._id ? 'Edit Category' : 'Add Category'}
</Typography> </Typography>
{isAdd && ( {isAdd && (
@@ -273,7 +272,7 @@ const tagLabelById = useMemo(() => {
required required
> >
{types.map((t) => { {types.map((t) => {
const value = t._id || t.id; // prefer Mongo _id for 1:1 mapping const value = t._id;
const label = t.typeName || value; const label = t.typeName || value;
return ( return (
<MenuItem key={value} value={value}> <MenuItem key={value} value={value}>
@@ -307,7 +306,7 @@ const tagLabelById = useMemo(() => {
sx={{ mb: 2 }} sx={{ mb: 2 }}
> >
{allTags.map((t) => { {allTags.map((t) => {
const value = t._id || t.id; const value = t._id;
const label = t.tagName || t.name || value; const label = t.tagName || t.name || value;
return ( return (
<MenuItem key={value} value={value}> <MenuItem key={value} value={value}>
@@ -359,7 +358,7 @@ const tagLabelById = useMemo(() => {
<MenuItem value="Inactive">Inactive</MenuItem> <MenuItem value="Inactive">Inactive</MenuItem>
</TextField> </TextField>
{form._Id || form.id ? ( {form._id ? (
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2, mt: 2 }}> <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2, mt: 2 }}>
<TextField label="Created At" value={formatDateSafe(form.createdAt)} InputProps={{ readOnly: true }} fullWidth /> <TextField label="Created At" value={formatDateSafe(form.createdAt)} InputProps={{ readOnly: true }} fullWidth />
<TextField label="Created By" value={form.createdBy ?? '—'} InputProps={{ readOnly: true }} fullWidth /> <TextField label="Created By" value={form.createdBy ?? '—'} InputProps={{ readOnly: true }} fullWidth />
@@ -369,7 +368,7 @@ 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) ? ( {form._id ? (
<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 }}>

View File

@@ -9,7 +9,6 @@ import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
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';
import TagTypeApi from '../../api/TagTypeApi';
export default function Categories() { export default function Categories() {
const { user } = useAuth(); const { user } = useAuth();
@@ -41,21 +40,25 @@ export default function Categories() {
setAllTags(list); setAllTags(list);
// Build a map of parentId -> array of child tagNames // Build a map of tagId -> tagName to resolve parent names
const parentToChildren = {}; const idToName = {};
for (const item of list) { for (const item of list) {
const parents = Array.isArray(item?.parentTagId) ? item.parentTagId : []; const key = item?._id || item?.id;
for (const pid of parents) { if (key) idToName[key] = item?.tagName || item?.name || '';
if (!parentToChildren[pid]) parentToChildren[pid] = [];
if (item?.tagName) parentToChildren[pid].push(item.tagName);
}
} }
// Enrich each row with `material` (children whose parentTagId includes this _id) // Enrich each row with `materialNames`: names of the parents referenced by parentTagId
const enriched = list.map((r) => ({ const enriched = list.map((r) => {
...r, const parents = Array.isArray(r?.parentTagId) ? r.parentTagId : [];
material: Array.isArray(parentToChildren[r?._id]) ? parentToChildren[r._id].join(', ') : '', const materialNames = parents
})); .map((pid) => idToName[pid])
.filter(Boolean);
return {
...r,
materialNames, // array of strings
};
});
setRows(enriched); setRows(enriched);
} catch (e) { } catch (e) {
@@ -73,8 +76,8 @@ export default function Categories() {
const r = params?.row; const r = params?.row;
if (!r) return; if (!r) return;
setEditingCategory({ setEditingCategory({
_Id: r._id || r._Id || '', _id: String(r._id || ''),
id: r.id || r.Id || '', id: String(r.id || ''),
tagName: r.tagName || r.name || '', tagName: r.tagName || r.name || '',
typeId: r.typeId || '', typeId: r.typeId || '',
parentTagId: Array.isArray(r.parentTagId) ? r.parentTagId : [], parentTagId: Array.isArray(r.parentTagId) ? r.parentTagId : [],
@@ -82,9 +85,11 @@ export default function Categories() {
displayOrder: Number(r.displayOrder ?? 0), displayOrder: Number(r.displayOrder ?? 0),
icon: r.icon || '', icon: r.icon || '',
status: r.status ?? 'Active', status: r.status ?? 'Active',
materialNames: typeof r.material === 'string' materialNames: Array.isArray(r.materialNames)
? r.material.split(',').map(s => s.trim()).filter(Boolean) ? r.materialNames
: Array.isArray(r.material) ? r.material : [], : (typeof r.material === 'string'
? r.material.split(',').map(s => s.trim()).filter(Boolean)
: []),
createdAt: r.createdAt ?? null, createdAt: r.createdAt ?? null,
createdBy: r.createdBy ?? null, createdBy: r.createdBy ?? null,
updatedAt: r.updatedAt ?? null, updatedAt: r.updatedAt ?? null,
@@ -100,7 +105,7 @@ export default function Categories() {
}; };
const pickHexId = (r) => const pickHexId = (r) =>
[r?._id, r?._Id, r?.id, r?.Id] [r?._id, r?.id]
.filter(Boolean) .filter(Boolean)
.find((x) => typeof x === 'string' && /^[0-9a-f]{24}$/i.test(x)) || null; .find((x) => typeof x === 'string' && /^[0-9a-f]{24}$/i.test(x)) || null;
@@ -178,7 +183,40 @@ export default function Categories() {
{ field: 'tagName', headerName: 'Name', flex: 1.2, minWidth: 180 }, { field: 'tagName', headerName: 'Name', flex: 1.2, minWidth: 180 },
{ field: 'slug', headerName: 'Slug', flex: 1.0, minWidth: 160 }, { field: 'slug', headerName: 'Slug', flex: 1.0, minWidth: 160 },
{ field: 'icon', headerName: 'Icon', flex: 0.7, minWidth: 250 }, { field: 'icon', headerName: 'Icon', flex: 0.7, minWidth: 250 },
/*
{ field: 'material', headerName: 'Material', flex: 1.2, minWidth: 200 }, { field: 'material', headerName: 'Material', flex: 1.2, minWidth: 200 },
*/
{
field: 'materialNames',
headerName: 'Material',
flex: 1.2,
minWidth: 220,
renderCell: (params) => {
const vals = Array.isArray(params?.row?.materialNames) ? params.row.materialNames : [];
return (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', alignItems: 'center' }}>
{vals.length ? vals.map((m) => (
<span
key={m}
style={{
padding: '2px 8px',
borderRadius: '12px',
background: '#DFCCBC',
color: '#26201A',
fontSize: 12,
lineHeight: '18px',
}}
>
{m}
</span>
)) : '—'}
</Box>
);
},
sortable: false,
filterable: false,
},
{ {
field: 'createdAt', field: 'createdAt',
headerName: 'Created Date', headerName: 'Created Date',