diff --git a/backend/src/api/users.ts b/backend/src/api/users.ts index 79dbbcac..452876db 100644 --- a/backend/src/api/users.ts +++ b/backend/src/api/users.ts @@ -120,6 +120,18 @@ class NewUser { } class UpdateUser { + @IsString() + @IsOptional() + firstName: string; + + @IsString() + @IsOptional() + lastName: string; + + @IsString() + @IsOptional() + fullName: string; + @IsString() @IsOptional() state: string; diff --git a/frontend/src/pages/Users/Users.tsx b/frontend/src/pages/Users/Users.tsx index a6140e28..88283dce 100644 --- a/frontend/src/pages/Users/Users.tsx +++ b/frontend/src/pages/Users/Users.tsx @@ -19,7 +19,7 @@ import { } from '@mui/material'; import { SelectChangeEvent } from '@mui/material/Select'; import { DataGrid, GridColDef, GridRenderCellParams } from '@mui/x-data-grid'; -import { Add, CheckCircleOutline, Delete } from '@mui/icons-material'; +import { Add, CheckCircleOutline, Edit, Delete } from '@mui/icons-material'; import CustomToolbar from 'components/DataGrid/CustomToolbar'; import ConfirmDialog from 'components/Dialog/ConfirmDialog'; import InfoDialog from 'components/Dialog/InfoDialog'; @@ -34,6 +34,7 @@ type ErrorStates = { getUsersError: string; getAddUserError: string; getDeleteError: string; + getUpdateUserError: string; }; export interface ApiResponse { @@ -49,6 +50,7 @@ interface UserType extends User { } type UserFormValues = { + id?: string; firstName: string; lastName: string; email: string; @@ -68,13 +70,15 @@ const initialUserFormValues = { type CloseReason = 'backdropClick' | 'escapeKeyDown' | 'closeButtonClick'; export const Users: React.FC = () => { - const { user, apiGet, apiPost, apiDelete } = useAuthContext(); + const { user, apiDelete, apiGet, apiPost, apiPut } = useAuthContext(); const [selectedRow, setSelectedRow] = useState(initializeUser); const [users, setUsers] = useState([]); const [newUserDialogOpen, setNewUserDialogOpen] = useState(false); + const [editUserDialogOpen, setEditUserDialogOpen] = useState(false); const [deleteUserDialogOpen, setDeleteUserDialogOpen] = useState(false); const [infoDialogOpen, setInfoDialogOpen] = useState(false); const [infoDialogContent, setInfoDialogContent] = useState(''); + const [formDisabled, setFormDisabled] = React.useState(true); const [formErrors, setFormErrors] = useState({ firstName: false, lastName: false, @@ -85,7 +89,8 @@ export const Users: React.FC = () => { const [errorStates, setErrorStates] = useState({ getUsersError: '', getAddUserError: '', - getDeleteError: '' + getDeleteError: '', + getUpdateUserError: '' }); const [values, setValues] = useState(initialUserFormValues); @@ -153,11 +158,14 @@ export const Users: React.FC = () => { headerName: 'Last Logged In', minWidth: 100, flex: 0.75 - }, - { - field: 'delete', - headerName: 'Delete', - minWidth: 100, + } + ]; + + if (user?.userType === 'globalAdmin') { + userCols.push({ + field: 'edit', + headerName: 'Edit', + minWidth: 50, flex: 0.4, renderCell: (cellValues: GridRenderCellParams) => { return ( @@ -165,15 +173,35 @@ export const Users: React.FC = () => { color="primary" onClick={() => { setSelectedRow(cellValues.row); - setDeleteUserDialogOpen(true); + setValues(cellValues.row); + setEditUserDialogOpen(true); }} > - + ); } + }); + } + userCols.push({ + field: 'delete', + headerName: 'Delete', + minWidth: 50, + flex: 0.4, + renderCell: (cellValues: GridRenderCellParams) => { + return ( + { + setSelectedRow(cellValues.row); + setDeleteUserDialogOpen(true); + }} + > + + + ); } - ]; + }); const addUserButton = user?.userType === 'globalAdmin' && ( { ); - const handleCloseAddUserDialog = (value: CloseReason) => { - if (value === 'backdropClick' || value === 'escapeKeyDown') { - return; - } + const onResetForm = () => { + setFormDisabled(false); + setEditUserDialogOpen(false); setNewUserDialogOpen(false); + setDeleteUserDialogOpen(false); + setInfoDialogOpen(false); + setValues(initialUserFormValues); setFormErrors({ firstName: false, lastName: false, @@ -200,13 +230,19 @@ export const Users: React.FC = () => { }); }; + const handleCloseAddUserDialog = (value: CloseReason) => { + if (value === 'backdropClick' || value === 'escapeKeyDown') { + return; + } + onResetForm(); + }; + const deleteRow = async (row: UserType) => { try { await apiDelete(`/users/${row.id}`, { body: {} }); setUsers(users.filter((user) => user.id !== row.id)); - setErrorStates({ ...errorStates, getUsersError: '' }); + setErrorStates({ ...errorStates, getDeleteError: '' }); setInfoDialogContent('This user has been successfully removed.'); - setDeleteUserDialogOpen(false); setInfoDialogOpen(true); } catch (e: any) { setErrorStates({ ...errorStates, getDeleteError: e.message }); @@ -217,7 +253,7 @@ export const Users: React.FC = () => { } }; - const onSubmit = async (e: any) => { + const onCreateUserSubmit = async (e: any) => { e.preventDefault(); console.log(e); const body = { @@ -248,7 +284,6 @@ export const Users: React.FC = () => { handleCloseAddUserDialog('closeButtonClick'); setInfoDialogContent('This user has been successfully added.'); setInfoDialogOpen(true); - setValues(initialUserFormValues); } catch (e: any) { setErrorStates({ ...errorStates, getAddUserError: e.message }); setInfoDialogContent( @@ -268,6 +303,7 @@ export const Users: React.FC = () => { ...values, [name]: value })); + setFormDisabled(false); }; const handleChange = (event: SelectChangeEvent) => { @@ -285,13 +321,36 @@ export const Users: React.FC = () => { } }; + const updateRow = async (row: UserFormValues) => { + try { + await apiPut(`/v2/users/${row.id}`, { body: { userType: row.userType } }); + const updateUsers = (prevUsers: any[]) => + prevUsers.map((user) => { + if (user.id === row.id) { + return { ...user, userType: row.userType }; + } + return user; + }); + setUsers(updateUsers(users)); + setErrorStates({ ...errorStates, getUpdateUserError: '' }); + setInfoDialogContent('This user has been successfully updated.'); + setInfoDialogOpen(true); + } catch (e: any) { + setErrorStates({ ...errorStates, getUpdateUserError: e.message }); + setInfoDialogContent( + 'This user has been not been updated. Check the console log for more details.' + ); + console.log(e); + } + }; + const confirmDeleteUserDialog = ( { deleteRow(selectedRow); }} - onCancel={() => setDeleteUserDialogOpen(false)} + onCancel={onResetForm} title={'Are you sure you want to delete this user?'} content={ <> @@ -311,6 +370,140 @@ export const Users: React.FC = () => { /> ); + const formContents = ( + + First Name + + Last Name + + Email + + State + + {formErrors.state && ( + + State is required + + )} + User Type + + } + label="Standard" + /> + } + label="Global View" + /> + } + label="Regional Administrator" + /> + } + label="Global Administrator" + /> + + {formErrors.userType && ( + + User Type is required + + )} + {errorStates.getAddUserError && ( + + Error adding user to the database: {errorStates.getAddUserError}. See + the network tab for more details. + + )} + + ); + + const confirmEditNotificationDialog = ( + updateRow(values)} + onCancel={onResetForm} + title={'Update User'} + content={formContents} + disabled={formDisabled} + /> + ); + return (

Users

@@ -332,122 +525,7 @@ export const Users: React.FC = () => { maxWidth="xs" > Invite a User - - First Name - - Last Name - - Email - - State - - {formErrors.state && ( - - State is required - - )} - User Type - - } - label="Standard" - /> - } - label="Global View" - /> - } - label="Regional Administrator" - /> - } - label="Global Administrator" - /> - - {formErrors.userType && ( - - User Type is required - - )} - {errorStates.getAddUserError && ( - - Error adding user to the database: {errorStates.getAddUserError}. - See the network tab for more details. - - )} - + {formContents} { > Cancel - + Invite User @@ -528,10 +610,11 @@ export const Users: React.FC = () => { /> )} + {confirmEditNotificationDialog} { - setInfoDialogOpen(false); + onResetForm(); window.location.reload(); }} icon={}