Skip to content

Commit

Permalink
Merge branch 'main' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
TeeGoood authored Aug 3, 2024
2 parents d48fd49 + 20c388c commit 031fee3
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 123 deletions.
117 changes: 103 additions & 14 deletions src/app/rpkm/staff/home/page.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,110 @@
'use client';

import Navbar from '@/components/rpkm/Navbar';
import Scan from '@/components/rpkm/staff/home/qrscanner/QRScanner';
import { useAuth } from '@/context/AuthContext';
import { createCheckIn, createCheckInByStudentId } from '@/utils/checkin';
import { getCurrentTime } from '@/utils/time';
import React, { useEffect, useState } from 'react';
import FailureModal from '@/components/rpkm/staff/home/qrscanner/failureModal';
import ConfirmationModal from '@/components/rpkm/staff/home/qrscanner/confirmationModal';
import { CheckIn } from '@/types/checkIn';
import { FRESHYNIGHT_EVENT, RPKM_DAY_1, RPKM_DAY_2 } from '@/utils/date';
import dayjs from 'dayjs';
import StudentCodeInput from '@/components/rpkm/staff/home/qrscanner/StudentCodeInput';

function Page() {
const [eventText, setEventText] = useState<string>('');
const { user } = useAuth();

//modal state
const [status, setStatus] = useState<'success' | 'error' | 'idle'>('idle');
const [taken, setTaken] = useState<boolean>(false);
const [error, setError] = useState<string | React.ReactNode>('');
const [errorTopic, setErrorTopic] = useState('');
const [checkInData, setCheckInData] = useState<CheckIn | null>(null);

useEffect(() => {
const initialize = async () => {
const currentTime = (await getCurrentTime()).currentTime;

const freshy_night_time = new Date(
process.env.NEXT_PUBLIC_FRESHY_NIGHT_EVENT as string
);
const rpkm_day_1_time = new Date(
process.env.NEXT_PUBLIC_RPKM_DAY_1 as string
);
const rpkm_day_2_time = new Date(
process.env.NEXT_PUBLIC_RPKM_DAY_2 as string
);

if (currentTime >= freshy_night_time) {
if (currentTime >= FRESHYNIGHT_EVENT) {
setEventText('Freshy Night');
} else if (currentTime >= rpkm_day_2_time) {
} else if (currentTime >= RPKM_DAY_1) {
setEventText('Onsite 4 สิงหาคม 2567');
} else if (currentTime >= rpkm_day_1_time) {
} else if (currentTime >= RPKM_DAY_2) {
setEventText('Onsite 3 สิงหาคม 2567');
}
};

initialize();
localStorage.setItem('enable', 'true');
}, []);

const handleCloseModal = () => {
setStatus('idle');
localStorage.setItem('enable', 'true');
};

const sendCheckInRequest = async (
mode: 'userId' | 'studentId',
id: string
) => {
if (!user || localStorage.getItem('enable') !== 'true') {
return;
}

//need to use localstorage to prevent user scaning multiple time
//don't use useState because it's not working with qrscanner
localStorage.setItem('enable', 'false');

let event = '';
const currentTime = (await getCurrentTime()).currentTime;

//need to check date every time because qr component don't update function when function re-render
if (currentTime >= FRESHYNIGHT_EVENT) {
event = 'freshy-night';
} else if (currentTime >= RPKM_DAY_2) {
event = 'rpkm-day-2';
} else if (currentTime >= RPKM_DAY_1) {
event = 'rpkm-day-1';
} else {
console.log('invalid date');
localStorage.setItem('enable', 'true');
return;
}

const newCheckInData: CheckIn | null =
mode == 'userId'
? await createCheckIn(id, user.email, event)
: await createCheckInByStudentId(id, user.email, event);

if (newCheckInData) {
if (newCheckInData.checkIn.isDuplicate) {
const date = dayjs(newCheckInData.checkIn.timestamp);
setStatus('error');
setError(
<div>
ผู้ใช้สแกน QR-code นี้แล้ว
<br />
{`เมื่อเวลา ${date.format('HH:mm')} น.`}
</div>
);
setErrorTopic('Already taken!');
setTaken(true);
return;
}

setCheckInData(newCheckInData);
setStatus('success');
} else {
setStatus('error');
setError('แสกนไม่สำเร็จ โปรดลองอีกครั้ง');
setErrorTopic('Invalid QR-code');
setTaken(false);
}
};

return (
<div className="bg-[#EAE3C3]">
<div className="bg-[url('/rpkm/staff/background.svg')] w-full bg-cover bg-no-repeat">
Expand Down Expand Up @@ -63,13 +135,30 @@ function Page() {
<div className="bg-[#183F86] w-4/5 text-white text-center rounded-r-full rounded-l-full">
{eventText}
</div>
<StudentCodeInput sendCheckInRequest={sendCheckInRequest} />
<div className="text-2xl font-semibold ">หรือ</div>
<div className="w-4/5 h-fit">
<Scan />
<Scan sendCheckInRequest={sendCheckInRequest} />
</div>
<div className="text-xl font-semibold">QR-Reader</div>
</div>
</div>
</div>

<ConfirmationModal
isOpen={status == 'success'}
userData={checkInData}
message="The check-in was successful."
onClose={handleCloseModal}
/>

<FailureModal
isOpen={status == 'error'}
message={error}
topic={errorTopic}
onClose={handleCloseModal}
taken={taken}
/>
</div>
);
}
Expand Down
130 changes: 21 additions & 109 deletions src/components/rpkm/staff/home/qrscanner/QRScanner.tsx
Original file line number Diff line number Diff line change
@@ -1,133 +1,45 @@
'use client';
import React, { ReactNode, useEffect, useState } from 'react';
import React from 'react';
import { QrReader } from 'react-qr-reader';
import { motion } from 'framer-motion';
import { useAuth } from '@/context/AuthContext';
import ConfirmationModal from './confirmationModal';
import FailureModal from './failureModal';
import { createCheckIn } from '@/utils/checkin';
import { CheckIn } from '@/types/checkIn';
import { getCurrentTime } from '@/utils/time';

const Scan: React.FC = () => {
const [checkInData, setCheckInData] = useState<CheckIn | null>(null);
const [taken, setTaken] = useState<boolean>(false);
const [status, setStatus] = useState<'success' | 'error' | 'idle'>('idle');
const { user } = useAuth();
const [error, setError] = useState<string | ReactNode>('');
const [errorTopic, setErrorTopic] = useState('');
interface ScanProps {
sendCheckInRequest: (
mode: 'userId' | 'studentId',
id: string
) => Promise<void>;
}

const Scan = ({ sendCheckInRequest }: ScanProps) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleScanResult = async (scanRawData: any) => {
if (!scanRawData || !user) {
const handleScanResult = (scanRawData: any) => {
if (!scanRawData || !scanRawData.text) {
return;
}

let event = '';
const currentTime = (await getCurrentTime()).currentTime;

//need to check date every time because qr component don't update function when function re-render
const freshy_night_time = new Date(
process.env.NEXT_PUBLIC_FRESHY_NIGHT_EVENT as string
);
const rpkm_day_1_time = new Date(
process.env.NEXT_PUBLIC_RPKM_DAY_1 as string
);
const rpkm_day_2_time = new Date(
process.env.NEXT_PUBLIC_RPKM_DAY_2 as string
);

if (currentTime > freshy_night_time) {
event = 'freshy-night';
} else if (currentTime > rpkm_day_2_time) {
event = 'rpkm-day-2';
} else if (currentTime > rpkm_day_1_time) {
event = 'rpkm-day-1';
}

//need to use localstorage to prevent user scan qr code multiple time
//i don't useState because it not work with qrscanner
const enable = localStorage.getItem('enable') === 'true';
if (!enable) return;

const userId = scanRawData.text;
const newCheckInData: CheckIn | null = await createCheckIn(
userId,
user.email,
event
);

localStorage.setItem('enable', 'false');

if (newCheckInData) {
/* if (newCheckInData.checkIn.isDuplicate) {
const date = dayjs(newCheckInData.checkIn.timestamp);
setStatus('error');
setError(
<div>
ผู้ใช้สแกน QR-code นี้แล้ว
<br />
{`เมื่อเวลา ${date.format('HH:mm')} น.`}
</div>
);
setErrorTopic('Already taken!');
setTaken(true);
return;
} */

setCheckInData(newCheckInData);
setStatus('success');
} else {
setStatus('error');
setError('แสกนไม่สำเร็จ โปรดลองอีกครั้ง');
setErrorTopic('Invalid QR-code');
setTaken(false);
}
sendCheckInRequest('userId', scanRawData.text);
};

const handleCloseModal = () => {
setStatus('idle');
};

useEffect(() => {
localStorage.setItem('enable', 'true');
}, []);

return (
<div className="flex flex-col items-center justify-center w-full">
<div className="relative w-full h-full">
<QrReader
className="bg-black"
onResult={handleScanResult}
constraints={{ facingMode: 'environment' }}
/>
<div className="overflow-hidden aspect-square">
<QrReader
scanDelay={0}
className="bg-black w-[100vw] aspect-square -mt-[13vw] -ml-[14vw]"
onResult={handleScanResult}
constraints={{ facingMode: 'environment' }}
/>
</div>

<motion.div
className="absolute left-1/2 top-1/2 h-48 w-48 max-w-md -translate-x-1/2 -translate-y-1/2 transform rounded-3xl border-4 border-white"
className="absolute left-1/2 top-1/2 h-[50vw] w-[50vw] max-w-md -translate-x-1/2 -translate-y-1/2 transform rounded-3xl border-4 border-white"
animate={{ opacity: [0.25, 0.5, 1, 0.5, 0.25] }}
transition={{ duration: 1, repeat: Infinity }}
></motion.div>
</div>
<div className="flex w-full items-center justify-center bg-black">
<div className="w-full bg-white text-center">
<ConfirmationModal
isOpen={status == 'success'}
userData={checkInData}
message="The check-in was successful."
onClose={handleCloseModal}
/>

<FailureModal
isOpen={status == 'error'}
message={error}
topic={errorTopic}
onClose={handleCloseModal}
taken={taken}
/>

{status == 'idle' && (
<p className="break-all text-black pt-2">Please scan a QR code</p>
)}
<p className="break-all text-black pt-2">Please scan a QR code</p>
</div>
</div>
</div>
Expand Down
52 changes: 52 additions & 0 deletions src/components/rpkm/staff/home/qrscanner/StudentCodeInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';

interface StudentCodeInputProps {
sendCheckInRequest: (
mode: 'userId' | 'studentId',
id: string
) => Promise<void>;
}

const StudentCodeInput = ({ sendCheckInRequest }: StudentCodeInputProps) => {
const [studentId, setStudentId] = useState<string>('');

const handleOnCheckIn = async () => {
if (studentId.length !== 10) {
toast.error('กรุณากรอกรหัสนิสิตให้ครบ 10 หลัก');
return;
}

console.log(studentId);
await sendCheckInRequest('studentId', studentId);
};

const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
if (!'0123456789'.includes(value.at(-1) || '') || value.length > 10) {
return;
}
setStudentId(value);
};

return (
<div className="flex flex-col items-center justify-center gap-2">
<div className="text-lg">กรอกรหัสนิสิต</div>
<input
className="w-full px-4 py-1 text-xl border rounded-md border-project-dark-blue focus:outline-none focus:border-blue-500"
type="text"
value={studentId}
onChange={handleOnChange}
placeholder="รหัสนิสิต"
/>
<button
className="text-xl px-4 bg-rpkm-blue text-white rounded-md focus:outline-none"
onClick={handleOnCheckIn}
>
submit
</button>
</div>
);
};

export default StudentCodeInput;
Loading

0 comments on commit 031fee3

Please sign in to comment.