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 {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;

View File

@@ -1,4 +1,3 @@
// src/api/userApi.js
export default class UserApi {
constructor(token) {
this.baseUrl = 'https://thalos-bff.dream-views.com/api/v1/User';
@@ -32,4 +31,40 @@ export default class UserApi {
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 { createExternalData, updateExternalData } from '../../api/mongo/actions';
import UserApi from '../../api/userApi';
import { useAuth } from '../../context/AuthContext';
export default function AddOrEditUserForm({ onAdd, initialData, onCancel }) {
const [formData, setFormData] = useState({
username: '',
fullName: '',
const { user } = useAuth();
const thalosToken = user?.thalosToken || localStorage.getItem('thalosToken');
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: '',
role: 'User',
status: 'Active'
});
name: '',
middleName: '',
lastName: '',
tenantId: '',
roleId: '',
status: 'Active',
companies: [],
projects: [],
sendInvitation: true,
});
}
}, [initialData]);
useEffect(() => {
if (initialData) {
setFormData({ ...initialData });
} else {
setFormData({
username: '',
fullName: '',
email: '',
role: 'User',
status: 'Active'
});
}
}, [initialData]);
const handleChange = (e) => {
const { name, value } = e.target;
if (name === 'companies' || name === 'projects') {
const arr = value.split(',').map(s => s.trim()).filter(s => s.length > 0);
setFormData(prev => ({ ...prev, [name]: arr }));
} else {
setFormData(prev => ({ ...prev, [name]: value }));
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = async () => {
try {
if (!api) throw new Error('Missing Thalos token');
const handleSubmit = async () => {
try {
if (initialData) {
await updateExternalData(formData);
} else {
await createExternalData(formData);
}
if (onAdd) onAdd();
} catch (error) {
console.error('Error submitting form:', error);
}
};
if (initialData) {
// UPDATE (PUT /User/Update) — API requires _Id, remove Id, displayName, tenantId
const payload = {
_Id: formData._Id,
email: formData.email,
name: formData.name,
middleName: formData.middleName,
lastName: formData.lastName,
tenantId: formData.tenantId,
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 (
<Box sx={{ py: 2 }}>
<TextField
fullWidth
label="Username"
name="username"
value={formData.username}
onChange={handleChange}
margin="normal"
/>
<TextField
fullWidth
label="Full Name"
name="fullName"
value={formData.fullName}
onChange={handleChange}
margin="normal"
/>
<TextField
fullWidth
label="Email"
name="email"
value={formData.email}
onChange={handleChange}
margin="normal"
/>
<TextField
fullWidth
select
label="Role"
name="role"
value={formData.role}
onChange={handleChange}
margin="normal"
>
<MenuItem value="Admin">Admin</MenuItem>
<MenuItem value="User">User</MenuItem>
<MenuItem value="Manager">Manager</MenuItem>
</TextField>
<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>
);
onAdd?.();
} catch (error) {
console.error('Error submitting form:', error);
}
};
return (
<Box sx={{ py: 2 }}>
<TextField
fullWidth
label="Email"
name="email"
value={formData.email}
onChange={handleChange}
margin="normal"
/>
<TextField
fullWidth
label="Name"
name="name"
value={formData.name}
onChange={handleChange}
margin="normal"
/>
<TextField
fullWidth
label="Middle Name"
name="middleName"
value={formData.middleName}
onChange={handleChange}
margin="normal"
/>
<TextField
fullWidth
label="Last Name"
name="lastName"
value={formData.lastName}
onChange={handleChange}
margin="normal"
/>
<TextField
fullWidth
label="Tenant Id"
name="tenantId"
value={formData.tenantId}
onChange={handleChange}
margin="normal"
/>
<TextField
fullWidth
label="Role Id"
name="roleId"
value={formData.roleId}
onChange={handleChange}
margin="normal"
/>
<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 UserApi from '../../api/userApi';
import { useAuth } from '../../context/AuthContext';
import { deleteExternalData } from '../../api/mongo/actions';
import useApiToast from '../../hooks/useApiToast';
import '../../App.css';
const columnsBase = [
{ field: 'email', headerName: 'Email', width: 260 },
@@ -88,7 +88,22 @@ export default function UserManagement() {
const handleEditClick = (params) => {
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);
};
@@ -100,7 +115,22 @@ export default function UserManagement() {
const handleConfirmDelete = async () => {
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();
} catch (error) {
console.error('Delete failed:', error);
@@ -164,12 +194,11 @@ export default function UserManagement() {
return (
<SectionContainer sx={{ width: '100%' }}>
<Typography variant="h4" gutterBottom color='#26201AFF'>User Management</Typography>
<Dialog open={open} onClose={() => { setOpen(false); setEditingData(null); }} maxWidth="md" fullWidth>
<DialogTitle>{editingData ? 'Edit User' : 'Add User'}</DialogTitle>
<DialogContent>
<AddOrEditUserForm
key={editingData?._id || editingData?.id || (open ? 'editing' : 'new')}
onAdd={async () => {
await loadData();
setOpen(false);
@@ -212,18 +241,18 @@ export default function UserManagement() {
sx={{
'& .MuiDataGrid-cell': {
display: 'flex',
alignItems: 'center', // vertical centering
alignItems: 'center',
},
'& .MuiDataGrid-columnHeader': {
display: 'flex',
alignItems: 'center', // vertical centering headers too
alignItems: 'center',
}
}}
/>
</Box>
<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
</Button>
</Box>