chore: add edit form

This commit is contained in:
Rodolfo Ruiz
2025-08-30 21:01:29 -06:00
parent 55cb3fb34f
commit bec10610e1
4 changed files with 257 additions and 104 deletions

View File

@@ -1,5 +1,5 @@
#root { #root {
max-width: 1280px;
margin: 0 auto; margin: 0 auto;
padding: 2rem; padding: 2rem;
text-align: center; text-align: center;

View File

@@ -1,4 +1,3 @@
// src/api/userApi.js
export default class UserApi { export default class UserApi {
constructor(token) { constructor(token) {
this.baseUrl = 'https://thalos-bff.dream-views.com/api/v1/User'; this.baseUrl = 'https://thalos-bff.dream-views.com/api/v1/User';
@@ -32,4 +31,40 @@ export default class UserApi {
throw err; throw err;
} }
} }
// === CREATE a user ===
async createUser(userData) {
try {
const response = await fetch(`${this.baseUrl}/Create`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error(`Failed to create user: ${response.status}`);
}
return await response.json();
} catch (err) {
console.error('Error creating user:', err);
throw err;
}
}
// === UPDATE a user ===
async updateUser(userData) {
try {
const response = await fetch(`${this.baseUrl}/Update`, {
method: 'PUT',
headers: this.getHeaders(),
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error(`Failed to update user: ${response.status}`);
}
return await response.json();
} catch (err) {
console.error('Error updating user:', err);
throw err;
}
}
} }

View File

@@ -1,103 +1,192 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useMemo } from 'react';
import { Box, Button, TextField, MenuItem } from '@mui/material'; import { Box, Button, TextField, MenuItem } from '@mui/material';
import { createExternalData, updateExternalData } from '../../api/mongo/actions'; import UserApi from '../../api/userApi';
import { useAuth } from '../../context/AuthContext';
export default function AddOrEditUserForm({ onAdd, initialData, onCancel }) { export default function AddOrEditUserForm({ onAdd, initialData, onCancel }) {
const [formData, setFormData] = useState({ const { user } = useAuth();
username: '', const thalosToken = user?.thalosToken || localStorage.getItem('thalosToken');
fullName: '', const api = useMemo(() => (thalosToken ? new UserApi(thalosToken) : null), [thalosToken]);
const [formData, setFormData] = useState({
_Id: '',
email: '',
name: '',
middleName: '',
lastName: '',
tenantId: '',
roleId: '',
status: 'Active',
companies: [],
projects: [],
sendInvitation: true,
});
useEffect(() => {
if (initialData) {
setFormData({
_Id: initialData._id || initialData._Id || '',
email: initialData.email ?? '',
name: initialData.name ?? '',
middleName: initialData.middleName ?? '',
lastName: initialData.lastName ?? '',
tenantId: initialData.tenantId ?? '',
roleId: initialData.roleId ?? '',
status: initialData.status ?? 'Active',
companies: Array.isArray(initialData.companies) ? initialData.companies : [],
projects: Array.isArray(initialData.projects) ? initialData.projects : [],
sendInvitation: true,
});
} else {
setFormData({
_Id: '',
email: '', email: '',
role: 'User', name: '',
status: 'Active' middleName: '',
}); lastName: '',
tenantId: '',
roleId: '',
status: 'Active',
companies: [],
projects: [],
sendInvitation: true,
});
}
}, [initialData]);
useEffect(() => { const handleChange = (e) => {
if (initialData) { const { name, value } = e.target;
setFormData({ ...initialData }); if (name === 'companies' || name === 'projects') {
} else { const arr = value.split(',').map(s => s.trim()).filter(s => s.length > 0);
setFormData({ setFormData(prev => ({ ...prev, [name]: arr }));
username: '', } else {
fullName: '', setFormData(prev => ({ ...prev, [name]: value }));
email: '', }
role: 'User', };
status: 'Active'
});
}
}, [initialData]);
const handleChange = (e) => { const handleSubmit = async () => {
const { name, value } = e.target; try {
setFormData(prev => ({ ...prev, [name]: value })); if (!api) throw new Error('Missing Thalos token');
};
const handleSubmit = async () => { if (initialData) {
try { // UPDATE (PUT /User/Update) — API requires _Id, remove Id, displayName, tenantId
if (initialData) { const payload = {
await updateExternalData(formData); _Id: formData._Id,
} else { email: formData.email,
await createExternalData(formData); name: formData.name,
} middleName: formData.middleName,
if (onAdd) onAdd(); lastName: formData.lastName,
} catch (error) { tenantId: formData.tenantId,
console.error('Error submitting form:', error); roleId: formData.roleId,
} companies: formData.companies,
}; projects: formData.projects,
status: formData.status || 'Active',
};
await api.updateUser(payload);
} else {
// CREATE (POST /User/Create)
const payload = {
email: formData.email,
name: formData.name,
middleName: formData.middleName,
lastName: formData.lastName,
roleId: formData.roleId,
sendInvitation: !!formData.sendInvitation,
};
await api.createUser(payload);
}
return ( onAdd?.();
<Box sx={{ py: 2 }}> } catch (error) {
<TextField console.error('Error submitting form:', error);
fullWidth }
label="Username" };
name="username"
value={formData.username} return (
onChange={handleChange} <Box sx={{ py: 2 }}>
margin="normal" <TextField
/> fullWidth
<TextField label="Email"
fullWidth name="email"
label="Full Name" value={formData.email}
name="fullName" onChange={handleChange}
value={formData.fullName} margin="normal"
onChange={handleChange} />
margin="normal" <TextField
/> fullWidth
<TextField label="Name"
fullWidth name="name"
label="Email" value={formData.name}
name="email" onChange={handleChange}
value={formData.email} margin="normal"
onChange={handleChange} />
margin="normal" <TextField
/> fullWidth
<TextField label="Middle Name"
fullWidth name="middleName"
select value={formData.middleName}
label="Role" onChange={handleChange}
name="role" margin="normal"
value={formData.role} />
onChange={handleChange} <TextField
margin="normal" fullWidth
> label="Last Name"
<MenuItem value="Admin">Admin</MenuItem> name="lastName"
<MenuItem value="User">User</MenuItem> value={formData.lastName}
<MenuItem value="Manager">Manager</MenuItem> onChange={handleChange}
</TextField> margin="normal"
<TextField />
fullWidth <TextField
select fullWidth
label="Status" label="Tenant Id"
name="status" name="tenantId"
value={formData.status} value={formData.tenantId}
onChange={handleChange} onChange={handleChange}
margin="normal" margin="normal"
> />
<MenuItem value="Active">Active</MenuItem> <TextField
<MenuItem value="Inactive">Inactive</MenuItem> fullWidth
</TextField> label="Role Id"
<Box display="flex" justifyContent="flex-end" mt={3} gap={1}> name="roleId"
<Button onClick={onCancel} className="button-transparent">Cancel</Button> value={formData.roleId}
<Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button> onChange={handleChange}
</Box> margin="normal"
</Box> />
); <TextField
fullWidth
label="Companies"
name="companies"
value={formData.companies.join(', ')}
onChange={handleChange}
margin="normal"
helperText="Comma-separated list"
/>
<TextField
fullWidth
label="Projects"
name="projects"
value={formData.projects.join(', ')}
onChange={handleChange}
margin="normal"
helperText="Comma-separated list"
/>
<TextField
fullWidth
select
label="Status"
name="status"
value={formData.status}
onChange={handleChange}
margin="normal"
>
<MenuItem value="Active">Active</MenuItem>
<MenuItem value="Inactive">Inactive</MenuItem>
</TextField>
<Box display="flex" justifyContent="flex-end" mt={3} gap={1}>
<Button onClick={onCancel} className="button-transparent">Cancel</Button>
<Button variant="contained" onClick={handleSubmit} className="button-gold">Save</Button>
</Box>
</Box>
);
} }

View File

@@ -7,8 +7,8 @@ import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
import AddOrEditUserForm from './AddOrEditUserForm'; import AddOrEditUserForm from './AddOrEditUserForm';
import UserApi from '../../api/userApi'; import UserApi from '../../api/userApi';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { deleteExternalData } from '../../api/mongo/actions';
import useApiToast from '../../hooks/useApiToast'; import useApiToast from '../../hooks/useApiToast';
import '../../App.css';
const columnsBase = [ const columnsBase = [
{ field: 'email', headerName: 'Email', width: 260 }, { field: 'email', headerName: 'Email', width: 260 },
@@ -88,7 +88,22 @@ export default function UserManagement() {
const handleEditClick = (params) => { const handleEditClick = (params) => {
if (!params || !params.row) return; if (!params || !params.row) return;
setEditingData(params.row); const r = params.row;
const normalized = {
_id: r._id || r._Id || '',
id: r.id || r.Id || '',
email: r.email ?? '',
name: r.name ?? '',
middleName: r.middleName ?? '',
lastName: r.lastName ?? '',
displayName: r.displayName ?? '',
tenantId: r.tenantId ?? '',
roleId: r.roleId ?? '',
status: r.status ?? 'Active',
companies: Array.isArray(r.companies) ? r.companies : [],
projects: Array.isArray(r.projects) ? r.projects : [],
};
setEditingData(normalized);
setOpen(true); setOpen(true);
}; };
@@ -100,7 +115,22 @@ export default function UserManagement() {
const handleConfirmDelete = async () => { const handleConfirmDelete = async () => {
try { try {
await deleteExternalData(rowToDelete?._id); if (!apiRef.current || !rowToDelete?._id) throw new Error('Missing API or user id');
const payload = {
_Id: rowToDelete._id || rowToDelete._Id,
Id: rowToDelete.id || rowToDelete.Id,
email: rowToDelete.email ?? '',
name: rowToDelete.name ?? '',
middleName: rowToDelete.middleName ?? '',
lastName: rowToDelete.lastName ?? '',
roleId: rowToDelete.roleId ?? '',
companies: Array.isArray(rowToDelete.companies) ? rowToDelete.companies : [],
projects: Array.isArray(rowToDelete.projects) ? rowToDelete.projects : [],
status: 'Inactive',
};
await apiRef.current.updateUser(payload);
await loadData(); await loadData();
} catch (error) { } catch (error) {
console.error('Delete failed:', error); console.error('Delete failed:', error);
@@ -164,12 +194,11 @@ export default function UserManagement() {
return ( return (
<SectionContainer sx={{ width: '100%' }}> <SectionContainer sx={{ width: '100%' }}>
<Typography variant="h4" gutterBottom color='#26201AFF'>User Management</Typography>
<Dialog open={open} onClose={() => { setOpen(false); setEditingData(null); }} maxWidth="md" fullWidth> <Dialog open={open} onClose={() => { setOpen(false); setEditingData(null); }} maxWidth="md" fullWidth>
<DialogTitle>{editingData ? 'Edit User' : 'Add User'}</DialogTitle> <DialogTitle>{editingData ? 'Edit User' : 'Add User'}</DialogTitle>
<DialogContent> <DialogContent>
<AddOrEditUserForm <AddOrEditUserForm
key={editingData?._id || editingData?.id || (open ? 'editing' : 'new')}
onAdd={async () => { onAdd={async () => {
await loadData(); await loadData();
setOpen(false); setOpen(false);
@@ -212,18 +241,18 @@ export default function UserManagement() {
sx={{ sx={{
'& .MuiDataGrid-cell': { '& .MuiDataGrid-cell': {
display: 'flex', display: 'flex',
alignItems: 'center', // vertical centering alignItems: 'center',
}, },
'& .MuiDataGrid-columnHeader': { '& .MuiDataGrid-columnHeader': {
display: 'flex', display: 'flex',
alignItems: 'center', // vertical centering headers too alignItems: 'center',
} }
}} }}
/> />
</Box> </Box>
<Box display="flex" justifyContent="flex-end" mt={2}> <Box display="flex" justifyContent="flex-end" mt={2}>
<Button variant="contained" onClick={() => setOpen(true)} className="button-gold"> <Button variant="contained" className="button-gold" onClick={() => setOpen(true)} >
Add User Add User
</Button> </Button>
</Box> </Box>