diff --git a/package-lock.json b/package-lock.json index 8b21dc79ef..8d9374a0a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@mui/x-data-grid": "^7.22.1", "@mui/x-date-pickers": "^7.22.1", "@pdfme/generator": "^5.0.0", + "@pdfme/schemas": "^5.1.6", "@reduxjs/toolkit": "^2.3.0", "@vitejs/plugin-react": "^4.3.2", "babel-plugin-transform-import-meta": "^2.2.1", diff --git a/src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx b/src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx index 40cf9e9dd7..8ca76393cd 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx @@ -1,10 +1,5 @@ import React from 'react'; -import { - fireEvent, - queryByLabelText, - render, - waitFor, -} from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { EventRegistrantsModal } from './EventRegistrantsModal'; import { EVENT_ATTENDEES, MEMBERS_LIST } from 'GraphQl/Queries/Queries'; @@ -46,7 +41,19 @@ const queryMockWithRegistrant = [ result: { data: { event: { - attendees: [{ _id: 'user1', firstName: 'John', lastName: 'Doe' }], + attendees: [ + { + _id: 'user1', + firstName: 'John', + lastName: 'Doe', + createdAt: '2023-01-01', + gender: 'Male', + birthDate: '1990-01-01', + eventsAttended: { + _id: 'event123', + }, + }, + ], }, }, }, @@ -69,9 +76,9 @@ const queryMockOrgMembers = [ _id: 'user1', firstName: 'John', lastName: 'Doe', - email: 'johndoe@palisadoes.com', - image: '', - createdAt: '12/12/22', + image: null, + email: 'johndoe@example.com', + createdAt: '2023-01-01', organizationsBlockedBy: [], }, ], @@ -81,6 +88,24 @@ const queryMockOrgMembers = [ }, }, ]; +const queryMockWithoutOrgMembers = [ + { + request: { + query: MEMBERS_LIST, + variables: { id: 'org123' }, + }, + result: { + data: { + organizations: [ + { + _id: 'org123', + members: [], + }, + ], + }, + }, + }, +]; const successfulAddRegistrantMock = [ { @@ -180,19 +205,18 @@ describe('Testing Event Registrants Modal', () => { await waitFor(() => expect( - queryByText('Please choose a user to add first!'), + queryByText('Please choose an user to add first!'), ).toBeInTheDocument(), ); // Choose a user to add as an attendee - const attendeeInput = queryByLabelText('Add a Registrant'); - if (attendeeInput) { - fireEvent.change(attendeeInput, { - target: { value: 'John Doe' }, - }); - fireEvent.keyDown(attendeeInput, { key: 'ArrowDown' }); - fireEvent.keyDown(attendeeInput, { key: 'Enter' }); - } + const attendeeInput = queryByLabelText('Add an Registrant'); + fireEvent.change(attendeeInput as Element, { + target: { value: 'John Doe' }, + }); + fireEvent.keyDown(attendeeInput as HTMLElement, { key: 'ArrowDown' }); + fireEvent.keyDown(attendeeInput as HTMLElement, { key: 'Enter' }); + fireEvent.click(queryByText('Add Registrant') as Element); await waitFor(() => @@ -234,7 +258,7 @@ describe('Testing Event Registrants Modal', () => { ); // Choose a user to add as an attendee - const attendeeInput = queryByLabelText('Add a Registrant'); + const attendeeInput = queryByLabelText('Add an Registrant'); fireEvent.change(attendeeInput as Element, { target: { value: 'John Doe' }, }); @@ -293,7 +317,7 @@ describe('Testing Event Registrants Modal', () => { }); test('Delete attendee mutation must fail properly', async () => { - const { queryByText, getByLabelText } = render( + const { queryByText, getByTestId } = render( { await waitFor(() => expect(queryByText('John Doe')).toBeInTheDocument()); - const deleteButton = getByLabelText('Delete'); + const deleteButton = getByTestId('CancelIcon'); fireEvent.click(deleteButton); await waitFor(() => @@ -332,4 +356,48 @@ describe('Testing Event Registrants Modal', () => { expect(queryByText('Error removing attendee')).toBeInTheDocument(), ); }); + test('Autocomplete functionality works correctly', async () => { + const { getByTitle, getByText, getByPlaceholderText } = render( + + + + + + + + + + + + , + ); + + // Wait for loading state to finish + await waitFor(() => { + const autocomplete = getByPlaceholderText( + 'Choose the user that you want to add', + ); + expect(autocomplete).toBeInTheDocument(); + }); + + // Test empty state with no options + const autocomplete = getByPlaceholderText( + 'Choose the user that you want to add', + ); + fireEvent.change(autocomplete, { target: { value: 'NonexistentUser' } }); + + await waitFor(() => { + expect(getByText('No Registrations found')).toBeInTheDocument(); + expect(getByText('Add Onspot Registration')).toBeInTheDocument(); + }); + + // Test clicking "Add Onspot Registration" + fireEvent.click(getByText('Add Onspot Registration')); + expect(getByText('Add Onspot Registration')).toBeInTheDocument(); + const closeButton = getByTitle('Close'); + fireEvent.click(closeButton); + }); }); diff --git a/src/components/EventRegistrantsModal/EventRegistrantsModal.tsx b/src/components/EventRegistrantsModal/EventRegistrantsModal.tsx index 68be43317d..d48d3b7439 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsModal.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { Modal, Button } from 'react-bootstrap'; import { toast } from 'react-toastify'; import { useMutation, useQuery } from '@apollo/client'; @@ -13,8 +13,8 @@ import Chip from '@mui/material/Chip'; import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import Autocomplete from '@mui/material/Autocomplete'; -import AddOnSpotAttendee from './AddOnSpotAttendee'; import { useTranslation } from 'react-i18next'; +import AddOnSpotAttendee from './AddOnSpotAttendee'; // Props for the EventRegistrantsModal component type ModalPropType = { @@ -30,6 +30,7 @@ interface InterfaceUser { firstName: string; lastName: string; } + /** * Modal component for managing event registrants. * Allows adding and removing attendees from an event. @@ -43,16 +44,10 @@ interface InterfaceUser { export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { const { eventId, orgId, handleClose, show } = props; const [member, setMember] = useState(null); - const [isAdding, setIsAdding] = useState(false); - const [addRegistrantMutation] = useMutation(ADD_EVENT_ATTENDEE, { - refetchQueries: [ - { query: EVENT_ATTENDEES, variables: { id: eventId } }, - { query: MEMBERS_LIST, variables: { id: orgId } }, - ], - }); const [open, setOpen] = useState(false); // Hooks for mutation operations + const [addRegistrantMutation] = useMutation(ADD_EVENT_ATTENDEE); const [removeRegistrantMutation] = useMutation(REMOVE_EVENT_ATTENDEE); // Translation hooks @@ -70,22 +65,16 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { variables: { id: eventId }, }); - const { - data: memberData, - loading: memberLoading, - refetch: memberRefetch, - } = useQuery(MEMBERS_LIST, { + const { data: memberData, loading: memberLoading } = useQuery(MEMBERS_LIST, { variables: { id: orgId }, - pollInterval: 5000, }); // Function to add a new registrant to the event const addRegistrant = (): void => { if (member == null) { - toast.warning('Please choose a user to add first!'); + toast.warning('Please choose an user to add first!'); return; } - setIsAdding(true); toast.warn('Adding the attendee...'); addRegistrantMutation({ variables: { @@ -102,9 +91,6 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { .catch((err) => { toast.error(t('errorAddingAttendee') as string); toast.error(err.message); - }) - .finally(() => { - setIsAdding(false); // Set loading state to false }); }; @@ -129,26 +115,11 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { }); }; - useEffect(() => { - if (show) { - const refetchInterval = setInterval(() => { - Promise.all([ - attendeesRefetch().catch((err) => - toast.error(`Failed to refresh attendees list: ${err.message}`), - ), - memberRefetch().catch((err) => - toast.error(`Failed to refresh members list: ${err.message}`), - ), - ]); - }, 5000); - - return () => clearInterval(refetchInterval); - } - }, [show, attendeesRefetch, memberRefetch]); + // Show a loading screen if data is still being fetched if (attendeesLoading || memberLoading) { return ( <> -
+
); } @@ -158,22 +129,27 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { setOpen(false)} - reloadMembers={() => { - memberRefetch(); - attendeesRefetch(); - }} + handleClose={ + /*istanbul ignore next */ + () => setOpen(false) + } + reloadMembers={ + /*istanbul ignore next */ + () => { + attendeesRefetch(); + } + } /> Event Registrants
Registered Registrants
- {attendeesData.event.attendees.length === 0 - ? 'There are no registered attendees for this event.' + {attendeesData.event.attendees.length == 0 + ? `There are no registered attendees for this event.` : null} - {attendeesData?.event?.attendees.map((attendee: InterfaceUser) => ( + {attendeesData.event.attendees.map((attendee: InterfaceUser) => ( {`${attendee.firstName[0]}${attendee.lastName[0]}`} @@ -181,8 +157,7 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { label={`${attendee.firstName} ${attendee.lastName}`} variant="outlined" key={attendee._id} - onDelete={() => deleteRegistrant(attendee._id)} - deleteIcon={×} + onDelete={(): void => deleteRegistrant(attendee._id)} /> ))} @@ -190,7 +165,7 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { { + onChange={(_, newMember): void => { setMember(newMember); }} noOptionsText={ @@ -210,10 +185,11 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { getOptionLabel={(member: InterfaceUser): string => `${member.firstName} ${member.lastName}` } - renderInput={(params) => ( + renderInput={(params): React.ReactNode => ( )} @@ -221,13 +197,8 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => {
-
diff --git a/src/screens/EventManagement/EventManagement.tsx b/src/screens/EventManagement/EventManagement.tsx index 60faceb21c..b78f03e16d 100644 --- a/src/screens/EventManagement/EventManagement.tsx +++ b/src/screens/EventManagement/EventManagement.tsx @@ -17,7 +17,6 @@ import VolunteerContainer from 'screens/EventVolunteers/VolunteerContainer'; import EventAgendaItems from 'components/EventManagement/EventAgendaItems/EventAgendaItems'; import useLocalStorage from 'utils/useLocalstorage'; import EventAttendance from 'components/EventManagement/EventAttendance/EventAttendance'; -import { EventRegistrantsWrapper } from 'components/EventRegistrantsModal/EventRegistrantsWrapper'; /** * List of tabs for the event dashboard. * @@ -232,9 +231,7 @@ const EventManagement = (): JSX.Element => { ); case 'registrants': return ( -
- -
+
Event Registrants
); case 'attendance': return (