Skip to content

Commit

Permalink
implemented multi-stage scanner (#602)
Browse files Browse the repository at this point in the history
* trial push

* added button

* modal added

* added modal form fields

* removed unnecessary code

* added chakra-ui

* form field display (error not fixed)

* feat: Added participand routes and hooks for registration-admin

* Did something

* proper post reuest set

* feat: added route for participant form submission in registration-admin

* registration completed

* Registration complete page added

* added cookie to check registration

* added endOfLine to prettier

* removed eol from prettier

* added markdown editor with save feature

* basic Emailer integration done

* feat: added multi-stage scanner

* moved multi-stage scanner to new route

---------

Co-authored-by: aaron-6960 <[email protected]>
Co-authored-by: aaron-6960 <[email protected]>
Co-authored-by: Subramani E <[email protected]>
Co-authored-by: Subramani E <[email protected]>
  • Loading branch information
5 people authored Dec 17, 2024
1 parent 516aed5 commit f616094
Show file tree
Hide file tree
Showing 8 changed files with 2,571 additions and 6,626 deletions.
13 changes: 12 additions & 1 deletion apps/core-admin/src/controllers/participants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ export const checkInParticipant = async (req: Request, res: Response) => {

const { orgId, eventId } = req?.params;

const { checkedInAt, checkInKey } = req?.body;
const { checkedInAt, checkInKey, assignedKey} = req?.body;

if (!checkedInAt || !checkInKey) {
return res.status(400).json({ error: 'checkInAt and checkInKey is required' });
Expand All @@ -844,6 +844,17 @@ export const checkInParticipant = async (req: Request, res: Response) => {
},
});

if (assignedKey && participantAlreadyCheckedIn) {
const updatedParticipant = await prisma.participant.update({
where: {
id: participantAlreadyCheckedIn.id,
},
data: {
assignedKey: assignedKey
}
})
}

if (!participantAlreadyCheckedIn) {
return res.status(404).json({ error: 'Participant not found' });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// vite.config.ts
import { defineConfig } from "file:///F:/Midhun/Programming/techno-event-management/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected]/node_modules/vite/dist/node/index.js";
import react from "file:///F:/Midhun/Programming/techno-event-management/node_modules/.pnpm/@[email protected][email protected]_@[email protected][email protected][email protected]_/node_modules/@vitejs/plugin-react/dist/index.mjs";
var vite_config_default = defineConfig({
plugins: [react()],
preview: {
host: true,
port: 3004
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJGOlxcXFxNaWRodW5cXFxcUHJvZ3JhbW1pbmdcXFxcdGVjaG5vLWV2ZW50LW1hbmFnZW1lbnRcXFxcYXBwc1xcXFxyZWdpc3RyYXRpb24tYWRtaW5cIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkY6XFxcXE1pZGh1blxcXFxQcm9ncmFtbWluZ1xcXFx0ZWNobm8tZXZlbnQtbWFuYWdlbWVudFxcXFxhcHBzXFxcXHJlZ2lzdHJhdGlvbi1hZG1pblxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRjovTWlkaHVuL1Byb2dyYW1taW5nL3RlY2huby1ldmVudC1tYW5hZ2VtZW50L2FwcHMvcmVnaXN0cmF0aW9uLWFkbWluL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSc7XHJcbmltcG9ydCByZWFjdCBmcm9tICdAdml0ZWpzL3BsdWdpbi1yZWFjdCc7XHJcblxyXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xyXG4gIHBsdWdpbnM6IFtyZWFjdCgpXSxcclxuICBwcmV2aWV3OiB7XHJcbiAgICBob3N0OiB0cnVlLFxyXG4gICAgcG9ydDogMzAwNCxcclxuICB9LFxyXG59KTtcclxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUE2WSxTQUFTLG9CQUFvQjtBQUMxYSxPQUFPLFdBQVc7QUFHbEIsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsU0FBUyxDQUFDLE1BQU0sQ0FBQztBQUFBLEVBQ2pCLFNBQVM7QUFBQSxJQUNQLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxFQUNSO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K
2 changes: 1 addition & 1 deletion apps/web-admin/src/components/Scanner.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const Scanner = ({ result, setResult }) => {
console.error(err);
};
const { ref } = useZxing({
onDecodeResult(result) {
onDecodeResult(result) {
setResult(result.getText());
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function CheckInParticipantWithScanner() {
{},
{
checkInKey,
assignedKey: null,
checkedInAt: new Date().toISOString(),
},
);
Expand Down Expand Up @@ -149,4 +150,4 @@ export default function CheckInParticipantWithScanner() {
</Flex>
</DashboardLayout>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ export default function ParticipantsCheckIn() {
previousPage={`/${orgId}/events/${eventId}/participants`}
headerButton={
<>
<Flex h="100%" flexDirection="column">
<Button
mt="auto"
mb="0.5"
onClick={() => {
router.push(`/${orgId}/events/${eventId}/participants/check-in/multi-in/scanner`);
}}
isLoading={loading}
>
Multi-Stage Scanner
</Button>
</Flex>
<Flex flexDirection="column" gap={4}>
<Button
onClick={() => {
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';

import { Button, FormControl, FormLabel, Select, Flex, Switch, Box, Text } from '@chakra-ui/react';

import DashboardLayout from '@/layouts/DashboardLayout';
import Scanner from '@/components/Scanner';

import { useAlert } from '@/hooks/useAlert';
import { useFetch } from '@/hooks/useFetch';
import useWrapper from '@/hooks/useWrapper';

export default function CheckInParticipantWithMultiScanner() {
const { loading, post, get } = useFetch();
const showAlert = useAlert();

const router = useRouter();
const { orgId, eventId } = router.query;

const { useGetQuery } = useWrapper();

const [stage, setStage] = useState(1);
const [previousCheckInKey, setPreviousCheckInKey] = useState(null);
const [checkInKey, setCheckInKey] = useState(null);
const [assignedKey, setAssignedKey] = useState(null);
const [scannedValue, setScannedValue] = useState(null);
const [participant, setParticipant] = useState(null);

const [fastMode, setFastMode] = useState(false);

const handleSubmit = async () => {
const { data, status } = await post(
`/core/organizations/${orgId}/events/${eventId}/participants/check-in`,
{},
{
checkInKey,
assignedKey,
checkedInAt: new Date().toISOString(),
},
);
if (status === 200) {
showAlert({
title: 'Success',
description: data.message,
status: 'success',
});
resetScanner(checkInKey);
} else {
showAlert({
title: 'Error',
description: data.error,
status: 'error',
});
resetScanner(null);
}
};

// reset states
const resetScanner = (value) => {
setStage(1);
setCheckInKey(null);
setPreviousCheckInKey(value);
setAssignedKey(null);
setScannedValue(null);
setParticipant(null);
};

// sets scanned value to corresponding state
const confirmScannedValue = () => {
if (stage == 1) {
setCheckInKey(scannedValue);
setScannedValue(null);
setStage(2);
} else if (stage == 2) {
setAssignedKey(scannedValue);
handleSubmit();
}
};

const { data, status, error } = useGetQuery(
`/core/organizations/${orgId}/events/${eventId}/participants/check-in/${checkInKey}`,
`/core/organizations/${orgId}/events/${eventId}/participants/check-in/${checkInKey}`,
{},
{},
(data) => {
setParticipant(data.data.participant);
},
);

useEffect(() => {
if (checkInKey && previousCheckInKey !== checkInKey && assignedKey && fastMode) {
handleSubmit();
}
}, [assignedKey]);

// in fast mode, when checkInKey is set, state is changed to scan assignedKey & an alert is shown
useEffect(() => {
if (fastMode && checkInKey && previousCheckInKey != checkInKey && !assignedKey) {
setAssignedKey(scannedValue);
setStage(2);
showAlert({
title: 'Scan Assigned Key QR Code',
description: 'Fast mode is enabled. Scan QR Code to assign Event Key to participant',
status: 'info',
});
}
}, [scannedValue]);

useEffect(() => {
if (fastMode) {
showAlert({
title: 'Fast Mode',
description: 'Fast mode is enabled. Participants will be checked in without confirmation.',
status: 'info',
});
}
}, [fastMode]);

//
// Periodically clear the previous participant id
//
useEffect(() => {
const intervalId = setInterval(() => {
setPreviousCheckInKey(null);
setCheckInKey(null);
setAssignedKey(null);
setParticipant(null);
}, 20000);

return () => clearInterval(intervalId);
}, [previousCheckInKey]);

return (
<DashboardLayout
pageTitle="Check In Participant"
previousPage={`/organizations/${orgId}/events/${eventId}/participants/check-in`}
debugInfo={checkInKey + ' ' + previousCheckInKey}
>
<Flex height="100%" width="100%" flexDirection="column" alignItems="center">
<Flex pb="6" justifyContent="center" alignItems="center" fontSize="xl" gap="1">
<Text>Current Stage: </Text>
<Text fontWeight="semibold">
Scanning {stage === 1 ? 'Check-In Key' : 'Assigned Key'}
</Text>
</Flex>
<Flex pb="3" justifyContent="center" alignItems="center" gap="4">
<Text fontSize="xl">Fast Mode</Text>
<Switch colorScheme="red" size="md" onChange={() => setFastMode(!fastMode)} />
</Flex>
<Box width={['100%', '60%', '50%', '30%']} pb="3">
<Scanner result={scannedValue} setResult={setScannedValue} />
</Box>
<Flex
flexDirection="column"
gap="0.8"
fontSize="lg"
justifyContent="center"
alignItems="center"
>
<Text>Scanned Value: {scannedValue ? scannedValue : 'None'}</Text>
<Text>Check-In Key: {checkInKey ? checkInKey : 'None'}</Text>
<Text>Assigned Key: {assignedKey ? assignedKey : 'None'}</Text>
</Flex>
{!fastMode && participant && (
<Flex width="100%" flexDirection="column" alignItems="center" gap="6">
<Flex gap="1" width="100%" justifyContent="space-between">
<Flex width="100%" flexDirection="column">
<Text>First Name: {participant?.firstName}</Text>
<Text>Last Name: {participant?.lastName}</Text>
<Text>Email: {participant?.email}</Text>
<Text>Phone: {participant?.phone}</Text>
</Flex>
<Flex width="100%" flexDirection="column">
{participant.checkIn.status ? (
<>
<Text color="red" fontSize="xl">
Checked In
</Text>
<Text>Checked in at: {participant.checkIn.at}</Text>
<Text>Checked in by: {participant.checkIn.by.email}</Text>
</>
) : (
<Text color="green" fontSize="xl">
Not Checked In
</Text>
)}
</Flex>
</Flex>
</Flex>
)}
<Flex gap="6" pt="3">
{scannedValue && (
<Button onClick={confirmScannedValue}>Confirm {stage === 1 ? 'Value' : ''}</Button>
)}
<Button
onClick={() => {
resetScanner(null);
}}
>
Clear
</Button>
</Flex>
</Flex>
</DashboardLayout>
);
}
Loading

0 comments on commit f616094

Please sign in to comment.