diff --git a/apps/core-admin/src/controllers/registration.ts b/apps/core-admin/src/controllers/registration.ts index f479fc50..1793c93f 100644 --- a/apps/core-admin/src/controllers/registration.ts +++ b/apps/core-admin/src/controllers/registration.ts @@ -10,12 +10,17 @@ export const orgAndEventVerification = async (req: Request, res: Response) => { where: { id: eventId, }, + select: { + organizationId: true, + isRegistrationClosed: true, + }, }); if (!event) { return res.status(404).json({ error: 'Event not found' }); - } - if (event.organizationId != orgId) { + } else if (event.organizationId != orgId) { return res.status(404).json({ error: "Organisation and event don't match" }); + } else if (event.isRegistrationClosed) { + return res.status(403).json({ error: 'Registration currently unavailable' }); } return res.status(200).json({ msg: 'Event corresponds to the given org and both exists' }); } catch (err: any) { @@ -29,6 +34,19 @@ export const addFormResponse = async (req: Request, res: Response) => { const { orgId, eventId } = req?.params; const data = req?.body; + const event = await prisma.event.findFirst({ + where: { + id: eventId, + }, + select: { + isShortlisting: true, + }, + }); + + if (!event) { + return res.status(404).json({ error: 'Event not found' }); + } + const defaultKeys = ['firstName', 'lastName', 'email', 'phone']; const defaultData: { [key: string]: string } = {}; const attrData: { attributeId: string; value: string }[] = []; @@ -44,21 +62,35 @@ export const addFormResponse = async (req: Request, res: Response) => { } } - const newRegistrant = await prisma.registrant.create({ - data: { - firstName: defaultData['firstName'], - lastName: defaultData['lastName'] || null, - email: defaultData['email'], - phone: defaultData['phone'] || null, - eventId: eventId, - organizationId: orgId, - registrantAttributes: { - create: attrData, - }, - }, - }); + const newAttendee = event.isShortlisting + ? await prisma.registrant.create({ + data: { + firstName: defaultData['firstName'], + lastName: defaultData['lastName'] || null, + email: defaultData['email'], + phone: defaultData['phone'] || null, + eventId: eventId, + organizationId: orgId, + registrantAttributes: { + create: attrData, + }, + }, + }) + : await prisma.participant.create({ + data: { + firstName: defaultData['firstName'], + lastName: defaultData['lastName'] || null, + email: defaultData['email'], + phone: defaultData['phone'] || null, + eventId: eventId, + organizationId: orgId, + participantAttributes: { + create: attrData, + }, + }, + }); - return res.status(200).json({ newRegistrant }); + return res.status(200).json({ newAttendee }); } catch (err: any) { console.error(err); return res.status(500).json({ error: 'Something went wrong' }); @@ -105,3 +137,41 @@ export const getFormAttributes = async (req: Request, res: Response) => { return res.status(500).json({ error: 'Something went wrong' }); } }; + +export const getAllRegistrations = async (req: Request, res: Response) => { + try { + const { orgId, eventId } = req?.params; + let registrants = await prisma.registrant.findMany({ + where: { + organizationId: orgId, + eventId, + }, + include: { + registrantAttributes: true, + registrantExtras: true, + }, + }); + + if (!registrants) { + return res.status(500).json({ error: 'Something went wrong' }); + } + + registrants = registrants.map((registrant: any) => { + return { + id: registrant.id, + addedAt: registrant.createdAt, + firstName: registrant.firstName, + lastName: registrant.lastName, + phone: registrant.phone, + email: registrant.email, + numberOfAttributesAssigned: registrant.registrantAttributes.length, + numnerOfExtrasAssigned: registrant.registrantExtras.length, + }; + }); + + return res.status(200).json({ registrants }); + } catch (err: any) { + console.error(err); + return res.status(500).json({ error: 'Something went wrong' }); + } +}; diff --git a/apps/core-admin/src/routes.ts b/apps/core-admin/src/routes.ts index 807bab33..3ef30c19 100644 --- a/apps/core-admin/src/routes.ts +++ b/apps/core-admin/src/routes.ts @@ -20,6 +20,7 @@ import { updateParticipantAttribute, getParticipantBycheckInKey, } from './controllers/participants'; +import { getAllRegistrations } from './controllers/registration'; import { addNewAttribute, editAttribute, @@ -79,6 +80,7 @@ router.get('/organizations/:orgId/events/:eventId', getEventStats); //midhun //m router.post('/organizations/:orgId/events', createNewEvent); //midhun router.get('/organizations/:orgId/events/:eventId/participants', getAllParticipants); //midhun //midhun - done +router.get('/organizations/:orgId/events/:eventId/registrations', getAllRegistrations); router.post('/organizations/:orgId/events/:eventId/participants', addNewParticipant); router.put('/organizations/:orgId/events/:eventId/participants/:participantId', editParticipant); diff --git a/apps/registration-admin/src/App.tsx b/apps/registration-admin/src/App.tsx index 1916ba08..a495177a 100644 --- a/apps/registration-admin/src/App.tsx +++ b/apps/registration-admin/src/App.tsx @@ -3,6 +3,7 @@ import { ChakraProvider } from '@chakra-ui/react'; import Form from './pages/Form'; import NotFound from './pages/NotFound'; import Registered from './pages/Registered'; +import RegistrationClosed from './pages/RegistrationClosed'; const App = () => { return ( @@ -11,6 +12,7 @@ const App = () => { } /> } /> + } /> } /> diff --git a/apps/registration-admin/src/hooks/useFetch.tsx b/apps/registration-admin/src/hooks/useFetch.tsx index 2bbeebf3..686af4f6 100644 --- a/apps/registration-admin/src/hooks/useFetch.tsx +++ b/apps/registration-admin/src/hooks/useFetch.tsx @@ -16,7 +16,13 @@ export const useFetch = () => { } catch (err) { console.error(err); setLoading(false); - return null; + if (axios.isAxiosError(err) && err.response) { + console.error(`Error: ${err.response.status} - ${err.response.statusText}`); + return { data: err.response.data, status: err.response.status }; + } else { + console.error('Error: Unable to process the request.'); + return null; + } } }; diff --git a/apps/registration-admin/src/pages/Form.tsx b/apps/registration-admin/src/pages/Form.tsx index 3bf8f1ab..07cc771f 100644 --- a/apps/registration-admin/src/pages/Form.tsx +++ b/apps/registration-admin/src/pages/Form.tsx @@ -31,6 +31,8 @@ const Form = () => { status: 'error', }); } + } else if (checkResponse?.status === 403) { + navigate('/registrationclosed'); } else { navigate('/'); } @@ -58,7 +60,7 @@ const Form = () => { description: 'Form submitted successfully!', status: 'success', }); - navigate('/'); + navigate('/already-registered'); } else { showAlert({ title: 'Error', diff --git a/apps/registration-admin/src/pages/NotFound.tsx b/apps/registration-admin/src/pages/NotFound.tsx index 292c61e4..7e8a2484 100644 --- a/apps/registration-admin/src/pages/NotFound.tsx +++ b/apps/registration-admin/src/pages/NotFound.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Box, Heading, Text, Center } from '@chakra-ui/react'; -const NotFound: React.FC = () => { +const NotFound: React.FC<{ msg?: String }> = ({ msg }) => { return (
@@ -9,7 +9,7 @@ const NotFound: React.FC = () => { 404 - Sorry, the page you are looking for does not exist. + {msg || 'Sorry, the page you are looking for does not exist.'}
diff --git a/apps/registration-admin/src/pages/RegistrationClosed.tsx b/apps/registration-admin/src/pages/RegistrationClosed.tsx new file mode 100644 index 00000000..4bb580de --- /dev/null +++ b/apps/registration-admin/src/pages/RegistrationClosed.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Box, Heading, Text, Center } from '@chakra-ui/react'; + +const RegistrationClosed: React.FC = () => { + return ( +
+ + + Registration Closed + + + The registrations for this event is not currently active + + +
+ ); +}; + +export default RegistrationClosed; diff --git a/apps/registration-admin/tsconfig.app.tsbuildinfo b/apps/registration-admin/tsconfig.app.tsbuildinfo index 5f74bc25..ae2e42d7 100644 --- a/apps/registration-admin/tsconfig.app.tsbuildinfo +++ b/apps/registration-admin/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/hooks/usealert.tsx","./src/hooks/usefetch.tsx","./src/pages/form.tsx","./src/pages/notfound.tsx","./src/pages/registered.tsx"],"version":"5.7.2"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/hooks/usealert.tsx","./src/hooks/usefetch.tsx","./src/pages/form.tsx","./src/pages/notfound.tsx","./src/pages/registered.tsx","./src/pages/registrationclosed.tsx"],"version":"5.7.2"} \ No newline at end of file diff --git a/apps/web-admin/src/contexts/MyContext.jsx b/apps/web-admin/src/contexts/MyContext.jsx index b6e14355..85979287 100644 --- a/apps/web-admin/src/contexts/MyContext.jsx +++ b/apps/web-admin/src/contexts/MyContext.jsx @@ -13,6 +13,7 @@ const MyContext = ({ children }) => { const { user, isAuthenticated, isLoading, loginWithRedirect } = useAuth0(); const [participants, setParticipants] = useState([]); const [userDetails, setUserDetails] = useState([]); + const [eventDetails, setEventDetails] = useState({}); const { loading, get, put } = useFetch(); const showAlert = useAlert(); @@ -76,6 +77,8 @@ const MyContext = ({ children }) => { updateAccountDetails, participants, setParticipants, + eventDetails, + setEventDetails, }} > {children} diff --git a/apps/web-admin/src/pages/[orgId]/events/[eventId]/navigationmenu.jsx b/apps/web-admin/src/pages/[orgId]/events/[eventId]/navigationmenu.jsx index b7a7d060..557b7d49 100644 --- a/apps/web-admin/src/pages/[orgId]/events/[eventId]/navigationmenu.jsx +++ b/apps/web-admin/src/pages/[orgId]/events/[eventId]/navigationmenu.jsx @@ -50,13 +50,22 @@ const NavigationMenu = ({ orgId, eventId }) => { width: { base: '100%', md: 'auto' }, }); const router = useRouter(); - const navItems = [ - { link: 'participants', name: 'Participants' }, - { link: 'check-in', name: 'Participants Check In' }, - { link: 'attributes', name: 'Attributes' }, - { link: 'extras', name: 'Extras' }, - ]; - const { activeTab, setActiveTab } = useContext(account); + const { activeTab, setActiveTab, eventDetails } = useContext(account); + console.log('trial', eventDetails.isShortlisting); + const navItems = + eventDetails.isShortlisting && router.asPath.endsWith('/participants') + ? [ + { link: 'registrants', name: 'Registrants' }, + { link: 'check-in', name: 'Participants Check In' }, + { link: 'attributes', name: 'Attributes' }, + { link: 'extras', name: 'Extras' }, + ] + : [ + { link: 'participants', name: 'Participants' }, + { link: 'check-in', name: 'Participants Check In' }, + { link: 'attributes', name: 'Attributes' }, + { link: 'extras', name: 'Extras' }, + ]; useEffect(() => { //console.log(activeTab); }, [activeTab]); @@ -94,24 +103,27 @@ const NavigationMenu = ({ orgId, eventId }) => { width="100%" display={{ base: 'none', md: 'flex' }} // Horizontal layout on desktop > - {['participants', 'check-in', 'attributes', 'extras'].map((tab) => ( - - ))} + ); + }} + > + {tab === 'check-in' + ? 'Participant Check In' + : tab.replace(/(^\w|\s\w)/g, (m) => m.toUpperCase())} + + ); + })} ); diff --git a/apps/web-admin/src/pages/[orgId]/events/[eventId]/registrants/index.jsx b/apps/web-admin/src/pages/[orgId]/events/[eventId]/registrants/index.jsx new file mode 100644 index 00000000..b5071a0d --- /dev/null +++ b/apps/web-admin/src/pages/[orgId]/events/[eventId]/registrants/index.jsx @@ -0,0 +1,193 @@ +import { useState, useEffect } from 'react'; +import { Button, useDisclosure } from '@chakra-ui/react'; +import { StyledBox, StyledText } from '@/components/ui/StyledComponents'; +import { useRouter } from 'next/router'; +import DashboardLayout from '@/layouts/DashboardLayout'; +import DataDisplay from '@/components/DataDisplay'; +import { useAlert } from '@/hooks/useAlert'; +import { useFetch } from '@/hooks/useFetch'; +import { CSVLink } from 'react-csv'; +import AddParticipant from '@/components/AddParticipant'; +import MultiStepModal from '@/components/MultiFormEmail'; +import { useContext } from 'react'; +import { account } from '@/contexts/MyContext'; +import axios from 'axios'; +import useWrapper from '@/hooks/useWrapper'; +import NavigationMenu from '../navigationmenu'; + +const columns = [ + { field: 'firstName', headerName: 'First Name', width: 200 }, + { field: 'lastName', headerName: 'Last Name', width: 200 }, + { field: 'email', headerName: 'Email', width: 200 }, + { field: 'phone', headerName: 'Phone', width: 200 }, + { field: 'numberOfAttributesAssigned', headerName: 'Attributes Assigned', width: 200 }, + { field: 'numnerOfExtrasAssigned', headerName: 'Extras Assigned', width: 200 }, + { field: 'addedAt', headerName: 'Added At', width: 200 }, +]; + +export default function Registrants() { + const { participants, setParticipants, accountDetails } = useContext(account); + const [formData, setFormData] = useState({ + firstName: '', + lastName: '', + email: '', + phone: '', + checkInKey: '', + }); + + //console.log("test",accountDetails); + + const { isOpen, onOpen, onClose } = useDisclosure(); + const router = useRouter(); + const showAlert = useAlert(); + const { orgId, eventId } = router.query; + // const { loading, get, post } = useFetch(); + const { useGetQuery, usePostMutation } = useWrapper(); + + // const { accountDetails } = useContext(account); + + const { + data, + status, + error, + isLoading: loading, + } = useGetQuery( + `/core/organizations/${orgId}/events/${eventId}/registrations`, + `/core/organizations/${orgId}/events/${eventId}/registrations`, + {}, + {}, + (data) => { + setParticipants(data.data.registrants || []); + }, + ); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData((prevData) => ({ ...prevData, [name]: value })); + }; + const [emailContent, setEmailContent] = useState(''); + const { mutate: addParticipantsMutation } = usePostMutation( + `/core/organizations/${orgId}/events/${eventId}/participants`, + {}, + { + onSuccess: (response) => { + const value = { + addedAt: response.data.newParticipant.createdAt, + id: response.data.newParticipant.id, + checkInKey: response.data.newParticipant.checkInKey, + email: response.data.newParticipant.email, + firstName: response.data.newParticipant.firstName, + lastName: response.data.newParticipant.lastName, + numberOfAttributesAssigned: 0, + numnerOfExtrasAssigned: 0, + phone: response.data.newParticipant.phone, + }; + setParticipants((prevValue) => [...prevValue, value]); + showAlert({ + title: 'Success', + description: 'Added participant!!', + status: 'success', + }); + }, + invalidatelKeys: [`/core/organizations/${orgId}/events/${eventId}/registrations`], + }, + ); + + const handleSubmit = async (e) => { + e.preventDefault(); + addParticipantsMutation({ + firstName: formData.firstName, + lastName: formData.lastName, + attributes: [], + phone: formData.phone, + email: formData.email, + checkInKey: formData.checkInKey, + }); + setFormData({ + firstName: '', + lastName: '', + email: '', + phone: '', + checkInKey: '', + }); + onClose(); + }; + const { isOpen: qrIsOpen, onOpen: qROnOpen, onClose: qROnClose } = useDisclosure(); + + const exportToCsv = () => { + const csvData = participants.map((participant) => ({ + firstName: participant.firstName, + lastName: participant.lastName, + email: participant.email, + phone: participant.phone, + checkInKey: participant.checkInKey, + checkedIn: participant.checkedIn, + numberOfAttributesAssigned: participant.numberOfAttributesAssigned, + numberOfExtrasAssigned: participant.numberOfExtrasAssigned, + addedAt: participant.addedAt, + })); + + return ( + + + + ); + }; + + return ( + + + + {exportToCsv()} + + + } + debugInfo={participants} + > + + + {!loading && participants.length === 0 ? ( + + + No participants + + + Add participants for the event to see details + + + ) : ( + <> + )} + + + + ); +} diff --git a/apps/web-admin/src/pages/[orgId]/events/index.jsx b/apps/web-admin/src/pages/[orgId]/events/index.jsx index af89fb2b..fd9cac4a 100644 --- a/apps/web-admin/src/pages/[orgId]/events/index.jsx +++ b/apps/web-admin/src/pages/[orgId]/events/index.jsx @@ -156,7 +156,7 @@ export default function Events() { const { isOpen, onOpen, onClose } = useDisclosure(); // const { loading, get } = useFetch(); const { useGetQuery } = useWrapper(); - const { accountDetails, setAccountDetails, allAccounts, setAllAccounts } = useContext(account); + const { accountDetails, setAccountDetails, setEventDetails } = useContext(account); // console.log(accountDetails.Event); const links = [ @@ -300,7 +300,10 @@ export default function Events() { columns={columns} rows={mergedEvents} onRowClick={(row) => { - router.push(`/${orgId}/events/${row.id}/participants`); + setEventDetails(row); + row.isShortlisting + ? router.push(`/${orgId}/events/${row.id}/registrants`) + : router.push(`/${orgId}/events/${row.id}/participants`); }} /> {!loading && events.length === 0 ? ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95be7789..da7943f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,7 +124,7 @@ importers: version: 0.0.0(eslint@8.56.0)(typescript@5.7.2) eslint-config-standard-with-typescript: specifier: ^43.0.0 - version: 43.0.1(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint-plugin-n@16.6.2(eslint@8.56.0))(eslint-plugin-promise@6.6.0(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.2) + version: 43.0.1(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0))(eslint-plugin-n@16.6.2(eslint@8.56.0))(eslint-plugin-promise@6.6.0(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.2) eslint-plugin-import: specifier: ^2.27.5 version: 2.31.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0) @@ -212,7 +212,7 @@ importers: version: 0.0.0(eslint@8.56.0)(typescript@5.7.2) eslint-config-standard-with-typescript: specifier: ^43.0.0 - version: 43.0.1(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint-plugin-n@16.6.2(eslint@8.56.0))(eslint-plugin-promise@6.6.0(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.2) + version: 43.0.1(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0))(eslint-plugin-n@16.6.2(eslint@8.56.0))(eslint-plugin-promise@6.6.0(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.2) eslint-plugin-import: specifier: ^2.27.5 version: 2.31.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0) @@ -13926,7 +13926,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-config-standard-with-typescript@43.0.1(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint-plugin-n@16.6.2(eslint@8.56.0))(eslint-plugin-promise@6.6.0(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.2): + eslint-config-standard-with-typescript@43.0.1(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0))(eslint-plugin-n@16.6.2(eslint@8.56.0))(eslint-plugin-promise@6.6.0(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.2): dependencies: '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.0.0(eslint@8.56.0)(typescript@5.7.2))(eslint@8.56.0)(typescript@5.7.2) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.7.2)