Leaderboard Added to HBContestPage

This commit is contained in:
ZacTohZY
2024-07-31 03:10:31 +08:00
parent 93d2cd8a05
commit b6fb95a5fe
4 changed files with 276 additions and 52 deletions

View File

@@ -539,3 +539,78 @@ export const ArrowTopRightOnSquare = () => {
</svg>
);
};
export const TrashDeleteIcon = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
className="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
);
}
export const EmailIcon = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
className="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"
/>
</svg>
)
}
export const InfoIcon = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
className="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
/>
</svg>
)
}
export const TrophyIcon = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
className="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.5 18.75h-9m9 0a3 3 0 0 1 3 3h-15a3 3 0 0 1 3-3m9 0v-3.375c0-.621-.503-1.125-1.125-1.125h-.871M7.5 18.75v-3.375c0-.621.504-1.125 1.125-1.125h.872m5.007 0H9.497m5.007 0a7.454 7.454 0 0 1-.982-3.172M9.497 14.25a7.454 7.454 0 0 0 .981-3.172M5.25 4.236c-.982.143-1.954.317-2.916.52A6.003 6.003 0 0 0 7.73 9.728M5.25 4.236V4.5c0 2.108.966 3.99 2.48 5.228M5.25 4.236V2.721C7.456 2.41 9.71 2.25 12 2.25c2.291 0 4.545.16 6.75.47v1.516M7.73 9.728a6.726 6.726 0 0 0 2.748 1.35m8.272-6.842V4.5c0 2.108-.966 3.99-2.48 5.228m2.48-5.492a46.32 46.32 0 0 1 2.916.52 6.003 6.003 0 0 1-5.395 4.972m0 0a6.726 6.726 0 0 1-2.749 1.35m0 0a6.772 6.772 0 0 1-3.044 0"
/>
</svg>
)
}

View File

@@ -1,5 +1,22 @@
import { Button } from "@nextui-org/react";
import { Button, Tooltip, Modal, ModalContent, ModalHeader, ModalBody } from "@nextui-org/react";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import instance from "../security/http";
import config from "../config";
import { InfoIcon, TrophyIcon } from "../icons";
interface User {
id: string;
firstName: string;
lastName: string;
}
interface CombinedData {
userId: string;
formId: string;
name: string;
avgBill: number;
}
export default function HBContestPage() {
const navigate = useNavigate();
@@ -15,12 +32,72 @@ export default function HBContestPage() {
}
};
const [combinedData, setCombinedData] = useState<CombinedData[]>([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const handleOpenModal = () => {
setIsModalOpen(true);
};
const [isInfoModalOpen, setIsInfoModalOpen] = useState(false);
const handleOpenInfoModal = () => setIsInfoModalOpen(true);
useEffect(() => {
const fetchData = async () => {
try {
// Fetch form data
const formDataResponse = await instance.get<CombinedData[]>(`${config.serverAddress}/hbcform`);
const processedFormData = formDataResponse.data;
// Fetch user information
const userInfoResponse = await instance.get<User[]>(`${config.serverAddress}/users/all`);
// Combine form data with user information
const combined = processedFormData.map((form) => {
const user = userInfoResponse.data.find((user) => user.id === form.userId);
return {
userId: user ? user.id : "Unknown User",
formId: form.userId,
name: user ? `${user.firstName} ${user.lastName}` : "Unknown Name",
avgBill: form.avgBill,
};
});
// Sort combined data by avgBill in ascending order
combined.sort((a, b) => a.avgBill - b.avgBill);
setCombinedData(combined);
} catch (error) {
console.log("Failed to fetch data");
}
};
fetchData();
}, []);
const topUser = combinedData.length > 0 ? combinedData[0] : null;
const top5Users = combinedData.slice(1, 10);
return (
<div className="h-screen flex items-center justify-center">
<section className="bg-red-50 dark:bg-primary-950 border border-primary-100 p-10 rounded-xl shadow-md w-full max-w-2xl">
<div className="flex flex-col items-center justify-center">
<div className="h-[700px] flex flex-col items-center justify-center overflow-y-auto p-4">
<section className="bg-red-50 dark:bg-primary-950 border border-primary-100 p-10 rounded-xl w-full max-w-2xl flex flex-col items-center">
<div className="w-full flex justify-end">
<Tooltip content="Information">
<Button isIconOnly variant="light" onPress={handleOpenInfoModal}>
<InfoIcon />
</Button>
</Tooltip>
<Tooltip content="Leaderboard">
<Button isIconOnly variant="light" onPress={handleOpenModal}>
<TrophyIcon />
</Button>
</Tooltip>
</div>
<div className="space-y-4">
<div>
<p className="text-2xl font-bold text-red-900 dark:text-white">
<p className="text-3xl font-bold text-red-900 dark:text-white">
Home Bill Contest
</p>
</div>
@@ -30,28 +107,26 @@ export default function HBContestPage() {
electricity and water usage. This contest would be won by the
person with the lowest overall bill average. Join us in this
important effort to create a more sustainable future for everyone.{" "}
<span className="text-red-600">
Participants would be required to input and upload their bills into the form to ensure integrity and honesty.{" "}
</span>
</p>
</div>
<div>
<p className="text-xl font-bold text-red-900 dark:text-white">
<p className="text-3xl font-bold text-red-900 dark:text-white">
Winners
</p>
</div>
<div>
<p className="text-gray-700 dark:text-gray-300">
There will 3 winners for each month. Each winner will receive
random food vouchers.
There will be 3 winners for each month. Each winner will receive
random vouchers.
</p>
<p className="text-gray-700 dark:text-gray-300">1st &rarr; 3 vouchers</p>
<p className="text-gray-700 dark:text-gray-300">2nd &rarr; 2 vouchers</p>
<p className="text-gray-700 dark:text-gray-300">3rd &rarr; 1 voucher</p>
<br></br>
<p className="text-gray-700 dark:text-gray-300 font-bold">1st Place &rarr; 3 vouchers</p>
<p className="text-gray-700 dark:text-gray-300 font-bold">2nd Place &rarr; 2 vouchers</p>
<p className="text-gray-700 dark:text-gray-300 font-bold">3rd Place &rarr; 1 voucher</p>
</div>
<div>
<div className="flex justify-end">
<Button
className="bg-red-300 text-white hover:bg-red-600 focus:ring-red-400 dark:bg-red-600 dark:hover:bg-red-900 dark:focus:ring-red-700 w-100"
className="bg-red-500 text-white hover:bg-red-900 focus:ring-red-400 dark:bg-red-600 dark:hover:bg-red-900 dark:focus:ring-red-700 w-100"
size="lg"
onPress={handleJoinClick}
>
@@ -61,5 +136,77 @@ export default function HBContestPage() {
</div>
</section>
</div>
<Modal isOpen={isModalOpen} onOpenChange={setIsModalOpen} isDismissable={false} isKeyboardDismissDisabled={true}>
<ModalContent className="w-full max-w-[400px] relative">
<ModalHeader className="flex justify-center items-center font-bold text-2xl text-red-900">
<span>Leaderboard</span>
</ModalHeader>
<ModalBody className="pb-8"> {/* Add padding-bottom for white space */}
<div className="flex flex-col gap-4">
{topUser && (
<div className="p-4 border rounded-md bg-red-100 flex items-center">
<TrophyIcon />
<p className="text-lg flex-1 text-center font-bold">{topUser.name}</p> {/* Center text */}
</div>
)}
</div>
<div className="grid grid-rows-1 md:grid-rows-2 gap-4">
{top5Users.map((user, index) => (
<div key={user.userId} className="p-4 border rounded-md bg-red-100 flex items-center">
<span className="text-xl font-bold w-8">{index + 2}</span>
<span className="flex-1 text-center">{user.name}</span>
</div>
))}
</div>
</ModalBody>
</ModalContent>
</Modal>
{/* Info Modal */}
<Modal
isOpen={isInfoModalOpen}
onOpenChange={setIsInfoModalOpen}
isDismissable={true}
isKeyboardDismissDisabled={true}
>
<ModalContent className="w-full max-w-[400px]">
<ModalHeader className="flex justify-between items-center font-bold text-2xl text-red-900">
Information
</ModalHeader>
<ModalBody className="pb-8">
<div className="space-y-4 text-gray-700 dark:text-gray-300">
<p className="font-semibold">Form Completion:</p>
<ul className="list-disc list-inside pl-4">
Participants must complete the online form, providing the following details
<li>Water bill amount</li>
<li>Electricity bill amount</li>
<li>Number of dependents</li>
</ul>
<p className="font-semibold">Document Upload</p>
<ul className="list-disc list-inside pl-4">
<li>Participants must upload clear, legible images of their water and electricity bills.</li>
<li>Both documents should clearly show the bill amount and date.</li>
</ul>
<p className="font-semibold">Data Accuracy</p>
<ul className="list-disc list-inside pl-4">
<li>All provided information must be accurate and truthful. Inaccurate or misleading information will result in disqualification.</li>
<li>All submitted bills and details will be subject to verification.</li>
</ul>
<p className="font-semibold">Privacy and Data Handling</p>
<ul className="list-disc list-inside pl-4">
<li>All personal data and bill images submitted will be handled in accordance with privacy regulations and will not be shared with third parties without consent.</li>
<li>Data will be used solely for contest purposes and to verify the validity of entries.</li>
</ul>
</div>
</ModalBody>
</ModalContent>
</Modal>
</div >
);
}

View File

@@ -139,12 +139,12 @@ export default function HBFormPage() {
return (
<div className="w-full h-full">
<section className="w-8/12 mx-auto">
<section className="w-7/12 mx-auto">
<Button variant="light" onPress={() => navigate(-1)}>
<ArrowUTurnLeftIcon />
</Button>
</section>
<section className="w-8/12 mx-auto p-5 bg-red-100 dark:bg-red-950 border border-primary-100 rounded-2xl h-600px">
<section className="w-7/12 mx-auto p-5 bg-red-100 dark:bg-red-950 border border-primary-100 rounded-2xl h-600px">
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
@@ -201,6 +201,7 @@ export default function HBFormPage() {
type="text"
placeholder="0"
labelPlacement="inside"
setFieldValue={setFieldValue}
/>
<NextUIFormikInput
label="Average Bill"

View File

@@ -12,23 +12,23 @@ module.exports = (sequelize, DataTypes) => {
},
electricalBill: {
type: DataTypes.DECIMAL(7, 2),
allowNull: false
allowNull: false,
},
waterBill: {
type: DataTypes.DECIMAL(7, 2),
allowNull: false
allowNull: false,
},
totalBill: {
type: DataTypes.DECIMAL(7, 2),
allowNull: false
allowNull: false,
},
noOfDependents: {
type: DataTypes.INTEGER,
allowNull: false
allowNull: false,
},
avgBill: {
type: DataTypes.DECIMAL(7, 2),
allowNull: false
allowNull: false,
},
ebPicture: {
type: DataTypes.BLOB("long"),
@@ -40,6 +40,7 @@ module.exports = (sequelize, DataTypes) => {
},
userId: {
type: DataTypes.UUID,
allowNull: false,
},
},
{