diff --git a/client/src/components/InsertImage.tsx b/client/src/components/InsertImage.tsx index 4fe7a9c..629411f 100644 --- a/client/src/components/InsertImage.tsx +++ b/client/src/components/InsertImage.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; interface InsertImageProps { - onImageSelected: (file: File) => void; + onImageSelected: (file: File | null) => void; } const InsertImage: React.FC = ({ onImageSelected }) => { @@ -10,22 +10,31 @@ const InsertImage: React.FC = ({ onImageSelected }) => { const handleImageSelect = (event: React.ChangeEvent) => { const selectedFiles = event.target.files as FileList; - const file = selectedFiles?.[0]; - if (file) { - setSelectedFile(file); - setPreviewImage(URL.createObjectURL(file)); - onImageSelected(file); - } + const file = selectedFiles?.[0] || null; + setSelectedFile(file); + setPreviewImage(file ? URL.createObjectURL(file) : ''); + onImageSelected(file); }; return ( -
- +
+ {selectedFile && ( - Selected Image + Selected Image )}
); }; -export default InsertImage; \ No newline at end of file +export default InsertImage; diff --git a/client/src/components/NextUIFormikInput.tsx b/client/src/components/NextUIFormikInput.tsx index 97bd714..8c5cd33 100644 --- a/client/src/components/NextUIFormikInput.tsx +++ b/client/src/components/NextUIFormikInput.tsx @@ -10,16 +10,28 @@ interface NextUIFormikInputProps { placeholder: string; labelPlacement?: "inside" | "outside"; startContent?: JSX.Element; + readOnly?: boolean; + setFieldValue?: (field: string, value: any, shouldValidate?: boolean) => void; } const NextUIFormikInput = ({ label, startContent, + readOnly = false, + setFieldValue, ...props }: NextUIFormikInputProps) => { const [field, meta] = useField(props.name); const [inputType, setInputType] = useState(props.type); + const handleChange = (e: React.ChangeEvent) => { + const { value } = e.target; + field.onChange(e); + if (setFieldValue) { + setFieldValue(props.name, value); + } + }; + return ( ) : null } + readOnly={readOnly} + onChange={handleChange} /> ); }; diff --git a/client/src/pages/HBContestPage.tsx b/client/src/pages/HBContestPage.tsx index b62ce9b..cc97f09 100644 --- a/client/src/pages/HBContestPage.tsx +++ b/client/src/pages/HBContestPage.tsx @@ -1,64 +1,65 @@ -import { - Card, - CardHeader, - CardBody, - CardFooter, - Divider, - Button, -} from "@nextui-org/react"; +import { Button } from "@nextui-org/react"; import { useNavigate } from "react-router-dom"; export default function HBContestPage() { const navigate = useNavigate(); + const handleJoinClick = () => { + let accessToken = localStorage.getItem("accessToken"); + if (!accessToken) { + setTimeout(() => { + navigate("/signin"); + }, 1000); + } else { + navigate("new-submission"); + } + }; + return ( -
-
- - -
-

Home Bill Contest

-
-
- - -

+

+
+
+
+

+ Home Bill Contest +

+
+
+

This contest is to encourage residents to reduce the use of 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. - Participants would be required to input and upload their bills - into the form to ensure integrity and honesty.{" "} + important effort to create a more sustainable future for everyone.{" "} + + Participants would be required to input and upload their bills into the form to ensure integrity and honesty.{" "} +

- - - -
-
-

Winners

-

- There will 3 winners for each month. Each winner will receive - random food vouchers. -

-

1st: 3 vouchers

-

2nd: 2 vouchers

-

3rd: 1 voucher

-
-
- -
-
-
- +
+
+

+ Winners +

+
+
+

+ There will 3 winners for each month. Each winner will receive + random food vouchers. +

+

1st → 3 vouchers

+

2nd → 2 vouchers

+

3rd → 1 voucher

+
+
+ +
+
); -} +} \ No newline at end of file diff --git a/client/src/pages/HBFormPage.tsx b/client/src/pages/HBFormPage.tsx index 841d8a1..96ba4a5 100644 --- a/client/src/pages/HBFormPage.tsx +++ b/client/src/pages/HBFormPage.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from 'react'; import { Button } from "@nextui-org/react"; import { ArrowUTurnLeftIcon } from "../icons"; import { useNavigate } from "react-router-dom"; @@ -8,29 +9,25 @@ import NextUIFormikInput from "../components/NextUIFormikInput"; import axios from "axios"; import InsertImage from "../components/InsertImage"; import { retrieveUserInformation } from "../security/users"; -import { useEffect, useState } from "react"; const validationSchema = Yup.object({ electricalBill: Yup.number() .typeError("Must be a number") .positive("Must be a positive value") .max(99999.99, "Value is too large") - .required(), + .required("Electrical bill is a required field"), waterBill: Yup.number() .typeError("Must be a number") .positive("Must be a positive value") .max(99999.99, "Value is too large") - .required(), - totalBill: Yup.number() - .typeError("Must be a number") - .positive("Must be a positive value") - .max(99999.99, "Value is too large") - .required(), + .required("Water bill is a required field"), noOfDependents: Yup.number() .typeError("Must be a number") .integer("Must be a whole number") .positive("Must be a positive value") - .required(), + .required("No. of dependents is a required field"), + ebPicture: Yup.mixed().required("Electrical bill picture is required"), + wbPicture: Yup.mixed().required("Water bill picture is required"), }); export default function HBFormPage() { @@ -41,11 +38,18 @@ export default function HBFormPage() { waterBill: "", totalBill: "", noOfDependents: "", + avgBill: "", ebPicture: null, wbPicture: null, userId: "", }); + // Add state for image selection + const [imagesSelected, setImagesSelected] = useState({ + ebPicture: false, + wbPicture: false, + }); + useEffect(() => { const getUserInformation = async () => { try { @@ -78,6 +82,7 @@ export default function HBFormPage() { formData.append("waterBill", values.waterBill); formData.append("totalBill", values.totalBill); formData.append("noOfDependents", values.noOfDependents); + formData.append("avgBill", values.avgBill); if (values.ebPicture) { formData.append("ebPicture", values.ebPicture); @@ -124,78 +129,117 @@ export default function HBFormPage() { } }; + // Handler for image selection + const handleImageSelection = (name: string, file: File | null) => { + setImagesSelected(prevState => ({ + ...prevState, + [name]: !!file, + })); + }; + return (
-
+
-
+
- {({ isValid, dirty, isSubmitting, setFieldValue }) => ( -
-
-
-
- - - - + {({ isValid, dirty, isSubmitting, setFieldValue, values }) => { + // Calculate the total bill + useEffect(() => { + const totalBill = Number(values.electricalBill) + Number(values.waterBill); + setFieldValue("totalBill", totalBill.toFixed(2)); + + const avgBill = Number(values.noOfDependents) > 0 + ? totalBill / Number(values.noOfDependents) + : 0; + setFieldValue("avgBill", avgBill.toFixed(2)); + + }, [values.electricalBill, values.waterBill, values.noOfDependents, setFieldValue]); + + // Disabled the submit button because the images field are not selected + const isSubmitDisabled = !imagesSelected.ebPicture || !imagesSelected.wbPicture; + + return ( + +
+
+
+ + + + + +
+
+ { + setFieldValue("ebPicture", file); + handleImageSelection("ebPicture", file); + }} + /> + { + setFieldValue("wbPicture", file); + handleImageSelection("wbPicture", file); + }} + /> +
-
- { - setFieldValue("ebPicture", file); - }} - /> - { - setFieldValue("wbPicture", file); - }} - /> +
+
-
- -
-
- - )} + + ); + }}
diff --git a/server/models/HBCform.js b/server/models/HBCform.js index 65e7aec..1d3bcb8 100644 --- a/server/models/HBCform.js +++ b/server/models/HBCform.js @@ -26,6 +26,10 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.INTEGER, allowNull: false }, + avgBill: { + type: DataTypes.DECIMAL(7, 2), + allowNull: false + }, ebPicture: { type: DataTypes.BLOB("long"), allowNull: true, diff --git a/server/routes/hbcform.js b/server/routes/hbcform.js index 85bd720..ba7de79 100644 --- a/server/routes/hbcform.js +++ b/server/routes/hbcform.js @@ -23,6 +23,7 @@ router.post("/", upload.fields([ waterBill: yup.number().positive().required(), totalBill: yup.number().positive().required(), noOfDependents: yup.number().integer().positive().required(), + avgBill: yup.number().positive().required(), }); try { data = await validationSchema.validate(data, { abortEarly: false }); @@ -48,6 +49,7 @@ router.get("/", async (req, res) => { { waterBill: { [Op.like]: `%${search}%` } }, { totalBill: { [Op.like]: `%${search}%` } }, { noOfDependents: { [Op.like]: `%${search}%` } }, + { avgBill: { [Op.like]: `%${search}%` } }, ]; } let list = await HBCform.findAll({