Optimized HBC form fetching
This commit is contained in:
@@ -1,9 +1,23 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import config from '../config';
|
||||
import instance from '../security/http';
|
||||
import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, SortDescriptor, Button, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react';
|
||||
import { EmailIcon, ImageIcon, TrashDeleteIcon } from '../icons';
|
||||
import NextUIFormikSelect2 from '../components/NextUIFormikSelect2';
|
||||
import { useEffect, useState } from "react";
|
||||
import config from "../config";
|
||||
import instance from "../security/http";
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableColumn,
|
||||
TableBody,
|
||||
TableRow,
|
||||
TableCell,
|
||||
SortDescriptor,
|
||||
Button,
|
||||
Modal,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
} from "@nextui-org/react";
|
||||
import { EmailIcon, ImageIcon, TrashDeleteIcon } from "../icons";
|
||||
import NextUIFormikSelect2 from "../components/NextUIFormikSelect2";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
@@ -56,7 +70,10 @@ export default function Ranking() {
|
||||
});
|
||||
const [isEmailModalOpen, setIsEmailModalOpen] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [selectedUser, setSelectedUser] = useState<{ email: string, name: string }>({ email: "", name: "" });
|
||||
const [selectedUser, setSelectedUser] = useState<{
|
||||
email: string;
|
||||
name: string;
|
||||
}>({ email: "", name: "" });
|
||||
const [selectedFormId, setSelectedFormId] = useState<string | null>(null); // Changed to string to match UUID type
|
||||
const [townCouncils, setTownCouncils] = useState<string[]>([]);
|
||||
const [selectedTownCouncil, setSelectedTownCouncil] = useState<string>("");
|
||||
@@ -66,7 +83,9 @@ export default function Ranking() {
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const formResponse = await instance.get<FormData[]>(`${config.serverAddress}/hbcform`);
|
||||
const formResponse = await instance.get<FormData[]>(
|
||||
`${config.serverAddress}/hbcform`
|
||||
);
|
||||
const processedFormData = formResponse.data.map((data) => ({
|
||||
...data,
|
||||
electricalBill: Number(data.electricalBill),
|
||||
@@ -77,10 +96,14 @@ export default function Ranking() {
|
||||
setOriginalFormData(processedFormData);
|
||||
setFilteredFormData(processedFormData);
|
||||
|
||||
const userResponse = await instance.get<User[]>(`${config.serverAddress}/users/all`);
|
||||
const userResponse = await instance.get<User[]>(
|
||||
`${config.serverAddress}/users/all`
|
||||
);
|
||||
setUserData(userResponse.data);
|
||||
|
||||
const townCouncilsResponse = await instance.get(`${config.serverAddress}/users/town-councils-metadata`);
|
||||
const townCouncilsResponse = await instance.get(
|
||||
`${config.serverAddress}/users/town-councils-metadata`
|
||||
);
|
||||
setTownCouncils(JSON.parse(townCouncilsResponse.data).townCouncils);
|
||||
} catch (error) {
|
||||
console.log("Failed to fetch data:", error);
|
||||
@@ -94,7 +117,9 @@ export default function Ranking() {
|
||||
useEffect(() => {
|
||||
const filtered = originalFormData.filter((data) => {
|
||||
const user = userData.find((user) => user.id === data.userId);
|
||||
return selectedTownCouncil ? user?.townCouncil === selectedTownCouncil : true;
|
||||
return selectedTownCouncil
|
||||
? user?.townCouncil === selectedTownCouncil
|
||||
: true;
|
||||
});
|
||||
setFilteredFormData(filtered);
|
||||
}, [selectedTownCouncil, originalFormData, userData]);
|
||||
@@ -116,7 +141,9 @@ export default function Ranking() {
|
||||
townCouncilTopUsers[formDataWithUser.userTownCouncil] = [];
|
||||
}
|
||||
|
||||
townCouncilTopUsers[formDataWithUser.userTownCouncil].push(formDataWithUser);
|
||||
townCouncilTopUsers[formDataWithUser.userTownCouncil].push(
|
||||
formDataWithUser
|
||||
);
|
||||
});
|
||||
|
||||
// Sort each town council's users by avgBill and pick the top 3
|
||||
@@ -168,10 +195,13 @@ export default function Ranking() {
|
||||
|
||||
const sendEmail = async () => {
|
||||
try {
|
||||
const response = await instance.post(`${config.serverAddress}/hbcform/send-homebill-contest-email`, {
|
||||
const response = await instance.post(
|
||||
`${config.serverAddress}/hbcform/send-homebill-contest-email`,
|
||||
{
|
||||
email: selectedUser.email,
|
||||
name: selectedUser.name,
|
||||
});
|
||||
}
|
||||
);
|
||||
console.log(response.data.message);
|
||||
setIsEmailModalOpen(false);
|
||||
} catch (error) {
|
||||
@@ -187,9 +217,15 @@ export default function Ranking() {
|
||||
const deleteForm = async () => {
|
||||
if (selectedFormId === null) return;
|
||||
try {
|
||||
await instance.delete(`${config.serverAddress}/hbcform/${selectedFormId}`);
|
||||
setOriginalFormData(originalFormData.filter((data) => data.id !== selectedFormId));
|
||||
setFilteredFormData(filteredFormData.filter((data) => data.id !== selectedFormId));
|
||||
await instance.delete(
|
||||
`${config.serverAddress}/hbcform/${selectedFormId}`
|
||||
);
|
||||
setOriginalFormData(
|
||||
originalFormData.filter((data) => data.id !== selectedFormId)
|
||||
);
|
||||
setFilteredFormData(
|
||||
filteredFormData.filter((data) => data.id !== selectedFormId)
|
||||
);
|
||||
setSelectedFormId(null);
|
||||
setIsDeleteModalOpen(false);
|
||||
} catch (error) {
|
||||
@@ -221,7 +257,7 @@ export default function Ranking() {
|
||||
};
|
||||
|
||||
const townCouncilGroups: Record<string, FormDataWithUser[]> = {};
|
||||
topUsers.forEach(user => {
|
||||
topUsers.forEach((user) => {
|
||||
if (!townCouncilGroups[user.userTownCouncil]) {
|
||||
townCouncilGroups[user.userTownCouncil] = [];
|
||||
}
|
||||
@@ -253,16 +289,26 @@ export default function Ranking() {
|
||||
|
||||
try {
|
||||
const voucherId = voucher.id;
|
||||
const response = await instance.put(`${config.serverAddress}/vouchers/update-quantity/${voucherId}`, {
|
||||
quantityToSubtract: 1
|
||||
});
|
||||
const response = await instance.put(
|
||||
`${config.serverAddress}/vouchers/update-quantity/${voucherId}`,
|
||||
{
|
||||
quantityToSubtract: 1,
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status !== 200) {
|
||||
console.error(`Failed to update voucher quantity for voucherId: ${voucher.id}`);
|
||||
console.error(
|
||||
`Failed to update voucher quantity for voucherId: ${voucher.id}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error updating voucher quantity for voucherId: ${voucher.id}`, error);
|
||||
throw new Error(`Failed to update voucher quantity for voucherId: ${voucher.id}`);
|
||||
console.error(
|
||||
`Error updating voucher quantity for voucherId: ${voucher.id}`,
|
||||
error
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to update voucher quantity for voucherId: ${voucher.id}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +319,6 @@ export default function Ranking() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleGiveVouchers = async () => {
|
||||
try {
|
||||
await assignVouchersToUsers(top3Users);
|
||||
@@ -317,7 +362,6 @@ export default function Ranking() {
|
||||
Give Vouchers
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -327,9 +371,7 @@ export default function Ranking() {
|
||||
<TableColumn>User Name</TableColumn>
|
||||
<TableColumn>Total Bill</TableColumn>
|
||||
<TableColumn>Dependents</TableColumn>
|
||||
<TableColumn>
|
||||
Average Bill
|
||||
</TableColumn>
|
||||
<TableColumn>Average Bill</TableColumn>
|
||||
<TableColumn>Bill Picture</TableColumn>
|
||||
<TableColumn>Actions</TableColumn>
|
||||
</TableHeader>
|
||||
@@ -343,19 +385,37 @@ export default function Ranking() {
|
||||
<TableCell>{data.noOfDependents}</TableCell>
|
||||
<TableCell>${data.avgBill.toFixed(2)}</TableCell>
|
||||
<TableCell>
|
||||
{data.billPicture ? (
|
||||
<Button isIconOnly variant="light" onPress={() => handleImageClick(`${config.serverAddress}/hbcform/billPicture/${data.id}`)}>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={() =>
|
||||
handleImageClick(
|
||||
`${config.serverAddress}/hbcform/billPicture/${data.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<ImageIcon />
|
||||
</Button>
|
||||
) : (
|
||||
<Button isIconOnly variant="light">
|
||||
<ImageIcon />
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="flex flex-row">
|
||||
<Button isIconOnly variant="light" className="text-blue-500" onClick={() => handleEmailClick(data.userEmail, data.userName)}><EmailIcon /></Button>
|
||||
<Button isIconOnly variant="light" color="danger" onClick={() => handleDeleteClick(data.id)}><TrashDeleteIcon /></Button>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
className="text-blue-500"
|
||||
onClick={() =>
|
||||
handleEmailClick(data.userEmail, data.userName)
|
||||
}
|
||||
>
|
||||
<EmailIcon />
|
||||
</Button>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
color="danger"
|
||||
onClick={() => handleDeleteClick(data.id)}
|
||||
>
|
||||
<TrashDeleteIcon />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
@@ -363,35 +423,61 @@ export default function Ranking() {
|
||||
</Table>
|
||||
|
||||
{/* Email Modal */}
|
||||
<Modal isOpen={isEmailModalOpen} onClose={() => setIsEmailModalOpen(false)}>
|
||||
<Modal
|
||||
isOpen={isEmailModalOpen}
|
||||
onClose={() => setIsEmailModalOpen(false)}
|
||||
>
|
||||
<ModalContent>
|
||||
<ModalHeader>Send Email</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>Are you sure you want to send an email to {selectedUser.name} ({selectedUser.email})?</p>
|
||||
<p>
|
||||
Are you sure you want to send an email to {selectedUser.name} (
|
||||
{selectedUser.email})?
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="primary" onClick={sendEmail}>Send</Button>
|
||||
<Button color="secondary" onClick={() => setIsEmailModalOpen(false)}>Cancel</Button>
|
||||
<Button color="primary" onClick={sendEmail}>
|
||||
Send
|
||||
</Button>
|
||||
<Button
|
||||
color="secondary"
|
||||
onClick={() => setIsEmailModalOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
{/* Delete Modal */}
|
||||
<Modal isOpen={isDeleteModalOpen} onClose={() => setIsDeleteModalOpen(false)}>
|
||||
<Modal
|
||||
isOpen={isDeleteModalOpen}
|
||||
onClose={() => setIsDeleteModalOpen(false)}
|
||||
>
|
||||
<ModalContent>
|
||||
<ModalHeader>Delete Entry</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>Are you sure you want to delete this entry?</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="danger" onClick={deleteForm}>Delete</Button>
|
||||
<Button color="secondary" onClick={() => setIsDeleteModalOpen(false)}>Cancel</Button>
|
||||
<Button color="danger" onClick={deleteForm}>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color="secondary"
|
||||
onClick={() => setIsDeleteModalOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
{/* Image Modal */}
|
||||
<Modal isOpen={isImageModalOpen} onClose={() => setIsImageModalOpen(false)}>
|
||||
<Modal
|
||||
isOpen={isImageModalOpen}
|
||||
onClose={() => setIsImageModalOpen(false)}
|
||||
>
|
||||
<ModalContent>
|
||||
<ModalBody>
|
||||
{modalImageUrl && <img src={modalImageUrl} alt="Bill Image" />}
|
||||
|
||||
@@ -116,6 +116,7 @@ router.get("/", async (req, res) => {
|
||||
let list = await HBCform.findAll({
|
||||
where: condition,
|
||||
order: [["createdAt", "ASC"]],
|
||||
attributes: { exclude: ["billPicture"] },
|
||||
});
|
||||
res.json(list);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user