Towncouncil filter on Leaderboard
This commit is contained in:
@@ -19,7 +19,7 @@ const InsertImage: React.FC<InsertImageProps> = ({ onImageSelected }) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col items-center p-5 bg-white dark:bg-zinc-800 rounded-md"
|
className="flex flex-col items-center p-5 bg-white dark:bg-zinc-800 rounded-md"
|
||||||
style={{ width: 350, height: 500 }}
|
style={{ width: 400, height: 500 }}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -30,7 +30,7 @@ const InsertImage: React.FC<InsertImageProps> = ({ onImageSelected }) => {
|
|||||||
<img
|
<img
|
||||||
src={previewImage}
|
src={previewImage}
|
||||||
alt="Selected Image"
|
alt="Selected Image"
|
||||||
className="w-full h-full object-cover rounded-md"
|
className="w-full h-[410px] object-cover rounded-md"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ interface User {
|
|||||||
id: string;
|
id: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
|
townCouncil: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CombinedData {
|
interface CombinedData {
|
||||||
@@ -16,6 +17,7 @@ interface CombinedData {
|
|||||||
formId: string;
|
formId: string;
|
||||||
name: string;
|
name: string;
|
||||||
avgBill: number;
|
avgBill: number;
|
||||||
|
townCouncil: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HBContestPage() {
|
export default function HBContestPage() {
|
||||||
@@ -33,15 +35,11 @@ export default function HBContestPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [combinedData, setCombinedData] = useState<CombinedData[]>([]);
|
const [combinedData, setCombinedData] = useState<CombinedData[]>([]);
|
||||||
|
const [filteredData, setFilteredData] = useState<CombinedData[]>([]);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
const handleOpenModal = () => {
|
|
||||||
setIsModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [isInfoModalOpen, setIsInfoModalOpen] = useState(false);
|
const [isInfoModalOpen, setIsInfoModalOpen] = useState(false);
|
||||||
|
const [townCouncils, setTownCouncils] = useState<string[]>([]);
|
||||||
const handleOpenInfoModal = () => setIsInfoModalOpen(true);
|
const [selectedTownCouncil, setSelectedTownCouncil] = useState<string>("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@@ -61,6 +59,7 @@ export default function HBContestPage() {
|
|||||||
formId: form.userId,
|
formId: form.userId,
|
||||||
name: user ? `${user.firstName} ${user.lastName}` : "Unknown Name",
|
name: user ? `${user.firstName} ${user.lastName}` : "Unknown Name",
|
||||||
avgBill: form.avgBill,
|
avgBill: form.avgBill,
|
||||||
|
townCouncil: user ? user.townCouncil : "Unknown Town Council",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,6 +67,11 @@ export default function HBContestPage() {
|
|||||||
combined.sort((a, b) => a.avgBill - b.avgBill);
|
combined.sort((a, b) => a.avgBill - b.avgBill);
|
||||||
|
|
||||||
setCombinedData(combined);
|
setCombinedData(combined);
|
||||||
|
setFilteredData(combined);
|
||||||
|
|
||||||
|
// Fetch town councils
|
||||||
|
const townCouncilsResponse = await instance.get(`${config.serverAddress}/users/town-councils-metadata`);
|
||||||
|
setTownCouncils(JSON.parse(townCouncilsResponse.data).townCouncils);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to fetch data");
|
console.log("Failed to fetch data");
|
||||||
}
|
}
|
||||||
@@ -76,8 +80,16 @@ export default function HBContestPage() {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const topUser = combinedData.length > 0 ? combinedData[0] : null;
|
useEffect(() => {
|
||||||
const top5Users = combinedData.slice(1, 10);
|
// Filter combined data based on selected town council
|
||||||
|
const filtered = combinedData.filter((data) =>
|
||||||
|
selectedTownCouncil ? data.townCouncil === selectedTownCouncil : true
|
||||||
|
);
|
||||||
|
setFilteredData(filtered);
|
||||||
|
}, [selectedTownCouncil, combinedData]);
|
||||||
|
|
||||||
|
const topUser = filteredData.length > 0 ? filteredData[0] : null;
|
||||||
|
const top5Users = filteredData.slice(1, 10);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center">
|
<div className="flex flex-col items-center justify-center">
|
||||||
@@ -85,12 +97,12 @@ export default function HBContestPage() {
|
|||||||
<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">
|
<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">
|
<div className="w-full flex justify-end">
|
||||||
<Tooltip content="Information">
|
<Tooltip content="Information">
|
||||||
<Button isIconOnly variant="light" onPress={handleOpenInfoModal}>
|
<Button isIconOnly variant="light" onPress={() => setIsInfoModalOpen(true)}>
|
||||||
<InfoIcon />
|
<InfoIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="Leaderboard">
|
<Tooltip content="Leaderboard">
|
||||||
<Button isIconOnly variant="light" onPress={handleOpenModal}>
|
<Button isIconOnly variant="light" onPress={() => setIsModalOpen(true)}>
|
||||||
<TrophyIcon />
|
<TrophyIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -142,19 +154,35 @@ export default function HBContestPage() {
|
|||||||
<ModalHeader className="flex justify-center items-center font-bold text-2xl text-red-900">
|
<ModalHeader className="flex justify-center items-center font-bold text-2xl text-red-900">
|
||||||
<span>Leaderboard</span>
|
<span>Leaderboard</span>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody className="pb-8"> {/* Add padding-bottom for white space */}
|
<ModalBody className="pb-8">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="mb-4">
|
||||||
|
{townCouncils.length > 0 && (
|
||||||
|
<select
|
||||||
|
value={selectedTownCouncil}
|
||||||
|
onChange={(e) => setSelectedTownCouncil(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">All locations</option>
|
||||||
|
{townCouncils.map((townCouncil) => (
|
||||||
|
<option key={townCouncil} value={townCouncil}>
|
||||||
|
{townCouncil}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
{topUser && (
|
{topUser && (
|
||||||
<div className="p-4 border rounded-md bg-red-100 flex items-center">
|
<div className="p-2 border rounded-md bg-red-100 dark:bg-primary-950 flex items-center">
|
||||||
<TrophyIcon />
|
<TrophyIcon />
|
||||||
<p className="text-lg flex-1 text-center font-bold">{topUser.name}</p> {/* Center text */}
|
<p className="text-lg flex-1 text-center font-bold">{topUser.name}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-rows-1 md:grid-rows-2 gap-4">
|
<div className="grid grid-rows-1 md:grid-rows-2 gap-2">
|
||||||
{top5Users.map((user, index) => (
|
{top5Users.map((user, index) => (
|
||||||
<div key={user.userId} className="p-4 border rounded-md bg-red-100 flex items-center">
|
<div key={user.userId} className="p-3 border rounded-md bg-red-100 dark:bg-primary-950 flex items-center">
|
||||||
<span className="text-xl font-bold w-8">{index + 2}</span>
|
<span className="text-lg font-bold w-8">{index + 2}</span>
|
||||||
<span className="flex-1 text-center">{user.name}</span>
|
<span className="flex-1 text-center">{user.name}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -178,35 +206,20 @@ export default function HBContestPage() {
|
|||||||
<div className="space-y-4 text-gray-700 dark:text-gray-300">
|
<div className="space-y-4 text-gray-700 dark:text-gray-300">
|
||||||
<p className="font-semibold">Form Completion:</p>
|
<p className="font-semibold">Form Completion:</p>
|
||||||
<ul className="list-disc list-inside pl-4">
|
<ul className="list-disc list-inside pl-4">
|
||||||
Participants must complete the online form, providing the following details
|
Participants must fill out all required fields to complete the form.
|
||||||
<li>Water bill amount</li>
|
|
||||||
<li>Electricity bill amount</li>
|
|
||||||
<li>Number of dependents</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
<p className="font-semibold">Eligibility:</p>
|
||||||
<p className="font-semibold">Document Upload</p>
|
|
||||||
<ul className="list-disc list-inside pl-4">
|
<ul className="list-disc list-inside pl-4">
|
||||||
<li>Participants must upload clear, legible images of their water and electricity bills.</li>
|
Residents must be registered with the town council to be eligible.
|
||||||
<li>Both documents should clearly show the bill amount and date.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
<p className="font-semibold">Submission Deadline:</p>
|
||||||
<p className="font-semibold">Data Accuracy</p>
|
|
||||||
<ul className="list-disc list-inside pl-4">
|
<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>
|
Ensure to submit the form before the end of the month.
|
||||||
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div >
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,12 +139,12 @@ export default function HBFormPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full">
|
<div className="w-full h-full">
|
||||||
<section className="w-7/12 mx-auto">
|
<section className="w-8/12 mx-auto p-6 bg-red-100 dark:bg-red-950 border border-primary-100 rounded-2xl h-600px">
|
||||||
|
<div className="py-2">
|
||||||
<Button variant="light" onPress={() => navigate(-1)}>
|
<Button variant="light" onPress={() => navigate(-1)}>
|
||||||
<ArrowUTurnLeftIcon />
|
<ArrowUTurnLeftIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</div>
|
||||||
<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
|
<Formik
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
@@ -170,7 +170,7 @@ export default function HBFormPage() {
|
|||||||
<Form>
|
<Form>
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<div className="flex flex-row gap-10">
|
<div className="flex flex-row gap-10">
|
||||||
<div className="flex flex-col gap-10">
|
<div className="flex flex-col gap-5 p-1">
|
||||||
<NextUIFormikInput
|
<NextUIFormikInput
|
||||||
label="Electrical Bill"
|
label="Electrical Bill"
|
||||||
name="electricalBill"
|
name="electricalBill"
|
||||||
|
|||||||
Reference in New Issue
Block a user