diff --git a/apps/e2e/cypress/e2e/eventLogs.cy.ts b/apps/e2e/cypress/e2e/eventLogs.cy.ts index 906cd5c590..7f91853293 100644 --- a/apps/e2e/cypress/e2e/eventLogs.cy.ts +++ b/apps/e2e/cypress/e2e/eventLogs.cy.ts @@ -67,37 +67,38 @@ context('Event log tests', () => { id: user.id, firstname: newFirstName, userTitle: 'Dr.', - lastname: 'Doe', + lastname: user.lastName, institutionId: 1, email: faker.internet.email(), - }); + }).then(() => { + cy.login('officer'); + cy.visit('/People'); - cy.login('officer'); - cy.visit('/People'); - - cy.finishedLoading(); + cy.finishedLoading(); - cy.get('[aria-label="Search"]').type(user.lastName); + cy.get('[data-cy="people-search"]').type(user.lastName); + cy.realPress('Enter'); - cy.contains(user.firstName) - .parent() - .find('button[aria-label="Edit user"]') - .click(); + cy.contains(user.lastName) + .parent() + .find('button[aria-label="Edit user"]') + .click(); - cy.get("[name='firstname']").should('have.value', newFirstName); + cy.get("[name='firstname']").should('have.value', newFirstName); - cy.get('[role="tab"]').contains('Logs').click(); + cy.get('[role="tab"]').contains('Logs').click(); - cy.get('[data-cy="event-logs-table"]').as('eventLogsTable'); - cy.get('@eventLogsTable') - .find('tr[level="0"]') - .last() - .as('eventLogsTableLastRow'); + cy.get('[data-cy="event-logs-table"]').as('eventLogsTable'); + cy.get('@eventLogsTable') + .find('tr[level="0"]') + .last() + .as('eventLogsTableLastRow'); - cy.get('@eventLogsTableLastRow').invoke('text').as('lastRowText'); + cy.get('@eventLogsTableLastRow').invoke('text').as('lastRowText'); - cy.get('@lastRowText').should('contain', 'USER_UPDATED'); - cy.get('@lastRowText').should('contain', updateProfileDate); + cy.get('@lastRowText').should('contain', 'USER_UPDATED'); + cy.get('@lastRowText').should('contain', updateProfileDate); + }); }); it('If user impersonates someone, it reflects in the action logs', () => { diff --git a/apps/e2e/cypress/e2e/generalFaps.cy.ts b/apps/e2e/cypress/e2e/generalFaps.cy.ts index 400ef93993..cf32a97674 100644 --- a/apps/e2e/cypress/e2e/generalFaps.cy.ts +++ b/apps/e2e/cypress/e2e/generalFaps.cy.ts @@ -66,7 +66,8 @@ context('General facility access panel tests', () => { this.skip(); } cy.contains('People').click(); - searchMuiTableAsync(fapMembers.chair.lastName); + cy.get('[data-cy="people-search"]').type(fapMembers.chair.lastName); + cy.realPress('Enter'); cy.get('[aria-label="Edit user"]').click(); cy.get('[cy-data="user-page"]').contains('Settings').click(); cy.contains('Add role').click(); @@ -82,9 +83,13 @@ context('General facility access panel tests', () => { variant: 'success', }); cy.contains('People').click(); + cy.get('[data-cy="people-search"]').type(fapMembers.secretary.lastName); + cy.realPress('Enter'); + cy.contains(fapMembers.secretary.lastName) + .parent() + .find('button[aria-label="Edit user"]') + .click(); - searchMuiTableAsync(fapMembers.secretary.lastName); - cy.get('[aria-label="Edit user"]').click(); cy.get('[cy-data="user-page"]').contains('Settings').click(); cy.contains('Add role').click(); diff --git a/apps/frontend/src/components/fap/Proposals/ProposalsView/AssignFapMemberToProposalModal.tsx b/apps/frontend/src/components/fap/Proposals/ProposalsView/AssignFapMemberToProposalModal.tsx index 02a2120b5a..e04a5da64c 100644 --- a/apps/frontend/src/components/fap/Proposals/ProposalsView/AssignFapMemberToProposalModal.tsx +++ b/apps/frontend/src/components/fap/Proposals/ProposalsView/AssignFapMemberToProposalModal.tsx @@ -90,6 +90,7 @@ const AssignFapMemberToProposalModal = ({ isLoading={loadingMembers} columns={columns} search + customSearch={false} onUpdate={(members: FapAssignedMember[]) => assignMembersToFapProposals( members, diff --git a/apps/frontend/src/components/proposal/PeopleSelectorModal.tsx b/apps/frontend/src/components/proposal/PeopleSelectorModal.tsx index 69c7ebb45d..f25b46ea74 100644 --- a/apps/frontend/src/components/proposal/PeopleSelectorModal.tsx +++ b/apps/frontend/src/components/proposal/PeopleSelectorModal.tsx @@ -68,6 +68,7 @@ const PeopleSelectorModal = ({ invitationUserRole: invitationUserRole || userRole, setPrincipalInvestigator: setPrincipalInvestigator, search: true, + customSearch: false, }; const peopleTablesProps = { diff --git a/apps/frontend/src/components/user/PeoplePage.tsx b/apps/frontend/src/components/user/PeoplePage.tsx index d81f9bd603..7a5f022034 100644 --- a/apps/frontend/src/components/user/PeoplePage.tsx +++ b/apps/frontend/src/components/user/PeoplePage.tsx @@ -31,7 +31,8 @@ export default function PeoplePage() { }} selection={false} showInvitationButtons - search + search={false} + customSearch={true} onRemove={(user: { id: number }) => api({ toastSuccessMessage: 'User removed successfully!', diff --git a/apps/frontend/src/components/user/PeopleTable.tsx b/apps/frontend/src/components/user/PeopleTable.tsx index bd9792012e..024a10b27c 100644 --- a/apps/frontend/src/components/user/PeopleTable.tsx +++ b/apps/frontend/src/components/user/PeopleTable.tsx @@ -5,8 +5,11 @@ import MaterialTableCore, { Query, QueryResult, } from '@material-table/core'; +import SearchIcon from '@mui/icons-material/Search'; +import { Grid, Tooltip } from '@mui/material'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import { Formik } from 'formik'; import React, { useState, useEffect, useContext } from 'react'; @@ -61,6 +64,7 @@ type PeopleTableProps = { isFreeAction?: boolean; data?: T[]; search?: boolean; + customSearch?: boolean; onRemove?: FunctionType; onUpdate?: FunctionType; emailInvite?: boolean; @@ -148,6 +152,7 @@ const PeopleTable = ({ mtOptions, onRemove, search, + customSearch, title, persistUrlQueryParams = false, }: PeopleTableProps) => { @@ -158,18 +163,17 @@ const PeopleTable = ({ subtractUsers: selectedUsers ? selectedUsers : [], userRole: userRole ? userRole : null, }); - const featureContext = useContext(FeatureContext); const isEmailSearchEnabled = !!featureContext.featuresMap.get( FeatureId.EMAIL_SEARCH )?.isEnabled; const api = useDataApi(); - const [currentPageIds, setCurrentPageIds] = useState([]); const [invitedUsers, setInvitedUsers] = useState([]); const [tableEmails, setTableEmails] = useState([]); const [searchParams, setSearchParams] = useSearchParams(); + const [searchName, setSearchName] = useState(''); const tableRef = React.createRef>(); @@ -186,6 +190,11 @@ const PeopleTable = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [data?.length]); + useEffect(() => { + tableRef.current && tableRef.current.onQueryChange({}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchParams.get('search')]); + const actionArray = []; action && !selection && @@ -270,7 +279,7 @@ const PeopleTable = ({ : orderBy?.orderDirection == PaginationSortDirection.DESC ? PaginationSortDirection.DESC : undefined, - searchText: tableQuery.search, + searchText: searchParams.get('search') ?? tableQuery.search, }) .then(({ users }) => { const filteredData = data @@ -320,210 +329,269 @@ const PeopleTable = ({ } }); + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + setSearchParams((searchParams) => { + if (searchName) { + searchParams.set('search', searchName); + } else { + searchParams.delete('search'); + } + + return searchParams; + }); + } else { + return searchParams; + } + }; + return ( - { - // If there is an email and it has not already been searched - if (values.email && !tableEmails.includes(values.email)) { - const userDetails = await getUserByEmail(values.email, api); - - if (!userDetails) { - setFieldError('email', 'No user found for the given email'); - - return; - } + + { + // If there is an email and it has not already been searched + if (values.email && !tableEmails.includes(values.email)) { + const userDetails = await getUserByEmail(values.email, api); - if (selectedUsers?.includes(userDetails.id)) { - setFieldError('email', 'User has already been added'); + if (!userDetails) { + setFieldError('email', 'No user found for the given email'); - return; - } + return; + } - if (invitedUsers.every((user) => user.id !== userDetails.id)) { - //Add users to the table - setInvitedUsers([userDetails].concat(invitedUsers)); - setTableEmails(tableEmails.concat([values.email])); - setFieldValue('email', ''); - - //If we are selecting multiple users add the user as pre selected. - if (selection) { - setSelectedParticipants?.( - selectedParticipants?.concat([userDetails]) || [] - ); + if (selectedUsers?.includes(userDetails.id)) { + setFieldError('email', 'User has already been added'); + + return; } - setQuery({ - ...query, - subtractUsers: (query.subtractUsers as number[]).concat( - userDetails.id - ), - }); - tableRef.current && tableRef.current.onQueryChange({}); + if (invitedUsers.every((user) => user.id !== userDetails.id)) { + //Add users to the table + setInvitedUsers([userDetails].concat(invitedUsers)); + setTableEmails(tableEmails.concat([values.email])); + setFieldValue('email', ''); + + //If we are selecting multiple users add the user as pre selected. + if (selection) { + setSelectedParticipants?.( + selectedParticipants?.concat([userDetails]) || [] + ); + } + + setQuery({ + ...query, + subtractUsers: (query.subtractUsers as number[]).concat( + userDetails.id + ), + }); + tableRef.current && tableRef.current.onQueryChange({}); + } else { + setFieldError('email', 'Could not add user to Proposal'); + } + } else if (tableEmails.includes(values.email)) { + setFieldError( + 'email', + 'User has already been added, possibly use filter instead' + ); } else { - setFieldError('email', 'Could not add user to Proposal'); + setFieldError('email', 'Please enter a email'); } - } else if (tableEmails.includes(values.email)) { - setFieldError( - 'email', - 'User has already been added, possibly use filter instead' - ); - } else { - setFieldError('email', 'Please enter a email'); - } - }} - > - - - {title} - - } - columns={setSortDirectionOnSortField( - columns ? columns : localColumns, - persistUrlQueryParams ? searchParams.get('sortField') : '', - sortDirection === PaginationSortDirection.ASC - ? PaginationSortDirection.ASC - : sortDirection === PaginationSortDirection.DESC - ? PaginationSortDirection.DESC - : undefined - )} - onSelectionChange={handleColumnSelectionChange} - data={fetchRemoteUsersData} - onPageChange={(page) => { - persistUrlQueryParams && - setSearchParams((searchParams) => { - searchParams.set('page', page.toString()); - - return searchParams; - }); - }} - onRowsPerPageChange={(pageSize) => { - persistUrlQueryParams && - setSearchParams((searchParams) => { - searchParams.set('pageSize', pageSize.toString()); - searchParams.set('page', '0'); - - return searchParams; - }); + { - persistUrlQueryParams && - setSearchParams((searchParams) => { - if (searchText) { - searchParams.set('search', searchText); - searchParams.set('page', '0'); - } else { - searchParams.delete('search'); - } + > + {customSearch && ( + + + ({ + marginRight: theme.spacing(1), + alignSelf: 'center', + fontSize: 'large', + color: theme.palette.primary.main, + })} + > + + setSearchName(e.target.value)} + sx={(theme) => ({ + flex: 1, + alignSelf: 'center', + maxWidth: 260, + marginRight: theme.spacing(3), + })} + /> + + )} - return searchParams; - }); - }} - onOrderCollectionChange={(orderByCollection) => { - const [orderBy] = orderByCollection; + + {title} + + } + columns={setSortDirectionOnSortField( + columns ? columns : localColumns, + persistUrlQueryParams ? searchParams.get('sortField') : '', + sortDirection === PaginationSortDirection.ASC + ? PaginationSortDirection.ASC + : sortDirection === PaginationSortDirection.DESC + ? PaginationSortDirection.DESC + : undefined + )} + onSelectionChange={handleColumnSelectionChange} + data={fetchRemoteUsersData} + onPageChange={(page) => { + persistUrlQueryParams && + setSearchParams((searchParams) => { + searchParams.set('page', page.toString()); - if (!orderBy) { + return searchParams; + }); + }} + onRowsPerPageChange={(pageSize) => { persistUrlQueryParams && setSearchParams((searchParams) => { - searchParams.delete('sortField'); - searchParams.delete('sortDirection'); + searchParams.set('pageSize', pageSize.toString()); + searchParams.set('page', '0'); return searchParams; }); - } else { + }} + onSearchChange={(searchText) => { persistUrlQueryParams && setSearchParams((searchParams) => { - searchParams.set('sortField', orderBy.orderByField); - searchParams.set('sortDirection', orderBy.orderDirection); + if (searchText) { + searchParams.set('search', searchText); + searchParams.set('page', '0'); + } else { + searchParams.delete('search'); + } return searchParams; }); - } - }} - options={{ - search: search, - searchText: persistUrlQueryParams - ? searchParams.get('search') || undefined - : undefined, - debounceInterval: 400, - selection: selection, - headerSelectionProps: { - inputProps: { 'aria-label': 'Select All Rows' }, - }, - pageSize: - persistUrlQueryParams && searchParams.get('pageSize') - ? +searchParams.get('pageSize')! + }} + onOrderCollectionChange={(orderByCollection) => { + const [orderBy] = orderByCollection; + + if (!orderBy) { + persistUrlQueryParams && + setSearchParams((searchParams) => { + searchParams.delete('sortField'); + searchParams.delete('sortDirection'); + + return searchParams; + }); + } else { + persistUrlQueryParams && + setSearchParams((searchParams) => { + searchParams.set('sortField', orderBy.orderByField); + searchParams.set('sortDirection', orderBy.orderDirection); + + return searchParams; + }); + } + }} + options={{ + search: search, + searchText: persistUrlQueryParams + ? searchParams.get('search') || undefined : undefined, - initialPage: - persistUrlQueryParams && searchParams.get('page') - ? +searchParams.get('page')! - : 0, - ...mtOptions, - selectionProps: (rowdata: BasicUserDetails) => ({ - inputProps: { - 'aria-label': `${rowdata.firstname}-${rowdata.lastname}-${rowdata.institution}-select`, + debounceInterval: 400, + selection: selection, + headerSelectionProps: { + inputProps: { 'aria-label': 'Select All Rows' }, }, - }), - }} - actions={actionArray} - editable={ - onRemove - ? { - onRowDelete: (oldData) => - new Promise((resolve) => { - resolve(); - (onRemove as FunctionType)(oldData); - }), - isDeletable: (rowData) => { - return getCurrentUser()?.user.id !== rowData.id; - }, - } - : {} - } - localization={{ - body: { emptyDataSourceMessage: 'No Users' }, - toolbar: { - nRowsSelected: '{0} User(s) Selected', - searchPlaceholder: 'Filter found users', - searchTooltip: 'Filter found users', - }, - }} - components={{ - Toolbar: - isEmailSearchEnabled && emailSearch - ? EmailSearchBar - : MTableToolbar, - }} - /> - {showInvitationButtons && ( - - {invitationButtons.map((item: InvitationButtonProps, i) => ( - - ))} - - )} - - + pageSize: + persistUrlQueryParams && searchParams.get('pageSize') + ? +searchParams.get('pageSize')! + : undefined, + initialPage: + persistUrlQueryParams && searchParams.get('page') + ? +searchParams.get('page')! + : 0, + ...mtOptions, + selectionProps: (rowdata: BasicUserDetails) => ({ + inputProps: { + 'aria-label': `${rowdata.firstname}-${rowdata.lastname}-${rowdata.institution}-select`, + }, + }), + }} + actions={actionArray} + editable={ + onRemove + ? { + onRowDelete: (oldData) => + new Promise((resolve) => { + resolve(); + (onRemove as FunctionType)(oldData); + }), + isDeletable: (rowData) => { + return getCurrentUser()?.user.id !== rowData.id; + }, + } + : {} + } + localization={{ + body: { emptyDataSourceMessage: 'No Users' }, + toolbar: { + nRowsSelected: '{0} User(s) Selected', + searchPlaceholder: 'Filter found users', + searchTooltip: 'Filter found users', + }, + }} + components={{ + Toolbar: + isEmailSearchEnabled && emailSearch + ? EmailSearchBar + : MTableToolbar, + }} + /> + {showInvitationButtons && ( + + {invitationButtons.map((item: InvitationButtonProps, i) => ( + + ))} + + )} + + + ); };