Modified HBForm & HBContest Page

This commit is contained in:
ZacTohZY
2024-07-29 21:26:34 +08:00
parent 34a96a8445
commit a76ec634ca
6 changed files with 203 additions and 129 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
interface InsertImageProps {
onImageSelected: (file: File) => void;
onImageSelected: (file: File | null) => void;
}
const InsertImage: React.FC<InsertImageProps> = ({ onImageSelected }) => {
@@ -10,22 +10,31 @@ const InsertImage: React.FC<InsertImageProps> = ({ onImageSelected }) => {
const handleImageSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
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 (
<div>
<input type="file" onChange={handleImageSelect} />
<div
className="flex flex-col items-center p-5 bg-white dark:bg-zinc-800 rounded-md"
style={{ width: 350, height: 500 }}
>
<input
type="file"
onChange={handleImageSelect}
className="mb-4"
/>
{selectedFile && (
<img src={previewImage} alt="Selected Image" />
<img
src={previewImage}
alt="Selected Image"
className="w-full h-full object-cover rounded-md"
/>
)}
</div>
);
};
export default InsertImage;
export default InsertImage;

View File

@@ -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<HTMLInputElement>) => {
const { value } = e.target;
field.onChange(e);
if (setFieldValue) {
setFieldValue(props.name, value);
}
};
return (
<Input
{...field}
@@ -43,6 +55,8 @@ const NextUIFormikInput = ({
</>
) : null
}
readOnly={readOnly}
onChange={handleChange}
/>
);
};

View File

@@ -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 (
<div className="w-full h-full">
<section>
<Card className="max-w-[800px] bg-red-50 mx-auto">
<CardHeader className="flex gap-3">
<div className="flex flex-col">
<h2 className="text-md">Home Bill Contest</h2>
</div>
</CardHeader>
<Divider />
<CardBody>
<p>
<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="space-y-4">
<div>
<p className="text-2xl font-bold text-red-900 dark:text-white">
Home Bill Contest
</p>
</div>
<div>
<p className="text-gray-700 dark:text-gray-300">
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.{" "}
<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>
</CardBody>
<Divider />
<CardFooter>
<div className="flex-col">
<div>
<h4>Winners</h4>
<p>
There will 3 winners for each month. Each winner will receive
random food vouchers.
</p>
<p>1st: 3 vouchers</p>
<p>2nd: 2 vouchers</p>
<p>3rd: 1 voucher</p>
</div>
<div>
<Button
className=" bg-red-500 dark:bg-red-700 text-white"
size="lg"
onPress={() => {
navigate("new-submission");
}}
>
<p className="font-bold">Join</p>
</Button>
</div>
</div>
</CardFooter>
</Card>
</div>
<div>
<p className="text-xl 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.
</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>
</div>
<div>
<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"
size="lg"
onPress={handleJoinClick}
>
<p>Join</p>
</Button>
</div>
</div>
</section>
</div>
);
}
}

View File

@@ -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 (
<div className="w-full h-full">
<section className="w-7/12 mx-auto">
<section className="w-8/12 mx-auto">
<Button variant="light" onPress={() => navigate(-1)}>
<ArrowUTurnLeftIcon />
</Button>
</section>
<section className="w-7/12 mx-auto p-5 bg-red-100 border border-none rounded-2xl h-600px">
<section className="w-8/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}
onSubmit={handleSubmit}
>
{({ isValid, dirty, isSubmitting, setFieldValue }) => (
<Form>
<div className="flex flex-col gap-5">
<div className="flex flex-row gap-10">
<div className="flex flex-col gap-5">
<NextUIFormikInput
label="Electrical Bill"
name="electricalBill"
type="text"
placeholder="$"
labelPlacement="inside"
/>
<NextUIFormikInput
label="Water Bill"
name="waterBill"
type="text"
placeholder="$"
labelPlacement="inside"
/>
<NextUIFormikInput
label="Total Bill"
name="totalBill"
type="text"
placeholder="$"
labelPlacement="inside"
/>
<NextUIFormikInput
label="Number of dependents"
name="noOfDependents"
type="text"
placeholder="0"
labelPlacement="inside"
/>
{({ 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 (
<Form>
<div className="flex flex-col gap-5">
<div className="flex flex-row gap-10">
<div className="flex flex-col gap-10">
<NextUIFormikInput
label="Electrical Bill"
name="electricalBill"
type="text"
placeholder="0.00"
labelPlacement="inside"
setFieldValue={setFieldValue}
/>
<NextUIFormikInput
label="Water Bill"
name="waterBill"
type="text"
placeholder="0.00"
labelPlacement="inside"
setFieldValue={setFieldValue}
/>
<NextUIFormikInput
label="Total Bill"
name="totalBill"
type="text"
placeholder="0.00"
labelPlacement="inside"
readOnly={true}
/>
<NextUIFormikInput
label="Number of dependents"
name="noOfDependents"
type="text"
placeholder="0"
labelPlacement="inside"
/>
<NextUIFormikInput
label="Average Bill"
name="avgBill"
type="text"
placeholder="0"
labelPlacement="inside"
readOnly={true}
/>
</div>
<div className="flex flex-row max-w-xs h-[500px] gap-10">
<InsertImage
onImageSelected={(file) => {
setFieldValue("ebPicture", file);
handleImageSelection("ebPicture", file);
}}
/>
<InsertImage
onImageSelected={(file) => {
setFieldValue("wbPicture", file);
handleImageSelection("wbPicture", file);
}}
/>
</div>
</div>
<div className="flex flex-row gap-8 max-w-xs h-[500px]">
<InsertImage
onImageSelected={(file) => {
setFieldValue("ebPicture", file);
}}
/>
<InsertImage
onImageSelected={(file) => {
setFieldValue("wbPicture", file);
}}
/>
<div>
<Button
type="submit"
className="bg-red-400 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-900 text-white"
size="lg"
isDisabled={!isValid || !dirty || isSubmitting || isSubmitDisabled}
>
<p>Submit</p>
</Button>
</div>
</div>
<div>
<Button
type="submit"
className="bg-red-500 dark:bg-red-700 text-white"
isDisabled={!isValid || !dirty || isSubmitting}
>
<p>Submit</p>
</Button>
</div>
</div>
</Form>
)}
</Form>
);
}}
</Formik>
</section>
</div>

View File

@@ -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,

View File

@@ -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({