chore: add edit form
This commit is contained in:
		| @@ -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; | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| @@ -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> | ||||||
|  |   ); | ||||||
| } | } | ||||||
| @@ -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> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Rodolfo Ruiz
					Rodolfo Ruiz