Added HBContest, HBForm

This commit is contained in:
ZacTohZY
2024-07-20 20:51:09 +08:00
parent eb4b026e18
commit 086572d171
8 changed files with 422 additions and 0 deletions

View File

@@ -14,6 +14,8 @@ import EventsPage from "./pages/EventsPage";
import CreateEventsPage from "./pages/CreateEventsPage";
import ManageEventsPage from "./pages/ManageEventsPage";
import AdministratorSpringboard from "./pages/AdministratorSpringboard";
import HBContestPage from "./pages/HBContestPage";
import HBFormPage from "./pages/HBFormPage";
import EditEventsPage from "./pages/EditEventsPage";
function App() {
@@ -32,6 +34,8 @@ function App() {
<Route element={<PostPage />} path="/post/:id" />
<Route element={<SchedulePage />} path="/schedule" />
<Route element={<EventsPage />} path="/events" />
<Route element={<HBContestPage />} path="/contest" />
<Route element={<HBFormPage />} path="/hbcform" />
<Route element={<CreateEventsPage />} path="/createEvent" />
<Route element={<ManageEventsPage />} path="/manageEvent" />
<Route element={<EditEventsPage />} path="/editEvent/:id" />

View File

@@ -0,0 +1,31 @@
import React, { useState } from 'react';
interface InsertImageProps {
onImageSelected: (file: File) => void;
}
const InsertImage: React.FC<InsertImageProps> = ({ onImageSelected }) => {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [previewImage, setPreviewImage] = useState<string>('');
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);
}
};
return (
<div>
<input type="file" onChange={handleImageSelect} />
{selectedFile && (
<img src={previewImage} alt="Selected Image" />
)}
</div>
);
};
export default InsertImage;

View File

@@ -91,6 +91,15 @@ export default function NavigationBar() {
>
<p className="text-lg">Schedules</p>
</Button>
<Button
variant="light"
size="sm"
onPress={() => {
navigate("/contest");
}}
>
<p className="text-lg">HB Contest</p>
</Button>
<Button
variant="light"
size="sm"

View File

@@ -0,0 +1,54 @@
import { Card, CardHeader, CardBody, CardFooter, Divider, Button } from "@nextui-org/react";
import DefaultLayout from '../layouts/default';
import { useNavigate } from 'react-router-dom';
export default function HBContestPage() {
let navigate = useNavigate();
return (
<DefaultLayout>
<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>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. </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("/hbcform");
}}
>
<p className="font-bold">Join</p>
</Button>
</div>
</div>
</CardFooter>
</Card>
</section>
</DefaultLayout>
)
}

View File

@@ -0,0 +1,168 @@
import DefaultLayout from '../layouts/default';
import { Button } from '@nextui-org/react';
import { ArrowUTurnLeftIcon } from '../icons';
import { useNavigate } from 'react-router-dom';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import config from '../config';
import NextUIFormikInput from '../components/NextUIFormikInput';
import axios from "axios";
import InsertImage from '../components/InsertImage';
const validationSchema = Yup.object({
electricalBill: Yup.number().positive().required(),
waterBill: Yup.number().positive().required(),
totalBill: Yup.number().positive().required(),
noOfDependents: Yup.number().integer().positive().required(),
});
export default function HBFormPage() {
let navigate = useNavigate();
const initialValues: {
id: string;
electricalBill: string;
waterBill: string;
totalBill: string;
noOfDependents: string;
ebPicture: File | null;
wbPicture: File | null;
} = {
id: '',
electricalBill: '',
waterBill: '',
totalBill: '',
noOfDependents: '',
ebPicture: null,
wbPicture: null,
};
const handleSubmit = async (
values: any,
{ setSubmitting, resetForm, setFieldError }: any
) => {
const formData = new FormData();
formData.append('electricalBill', values.electricalBill);
formData.append('waterBill', values.waterBill);
formData.append('totalBill', values.totalBill);
formData.append('noOfDependents', values.noOfDependents);
// Convert image files to blobs
const ebPictureFile = values.ebPicture;
const wbPictureFile = values.wbPicture;
if (ebPictureFile) {
const ebPictureBlob = ebPictureFile.slice(0, ebPictureFile.size, ebPictureFile.type);
formData.append('ebPicture', ebPictureBlob);
}
if (wbPictureFile) {
const wbPictureBlob = wbPictureFile.slice(0, wbPictureFile.size, wbPictureFile.type);
formData.append('wbPicture', wbPictureBlob);
}
try {
const response = await axios.post(config.serverAddress + "/hbcform", formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
if (response.status === 200) {
console.log("Form created successfully:", response.data);
resetForm(); // Clear form after successful submit
navigate("/contest");
} else {
console.error("Error creating form:", response.statusText);
}
} catch (error: any) {
if (error.response && error.response.data && error.response.data.field) {
setFieldError(error.response.data.field, error.response.data.error);
} else {
console.error("Unexpected error:", error);
}
} finally {
setSubmitting(false);
}
};
return (
<DefaultLayout>
<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 max-h-m">
<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"
/>
</div>
<div className="flex flex-row gap-10 max-w-xs max-h-xs">
<InsertImage
onImageSelected={(file) => {
setFieldValue('ebPicture', file);
}}
/>
<InsertImage
onImageSelected={(file) => {
setFieldValue('wbPicture', file);
}}
/>
</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>
)}
</Formik>
</section>
</DefaultLayout>
);
}

View File

@@ -34,6 +34,9 @@ app.use("/post", postRoute);
const schedulesRoute = require("./routes/schedule");
app.use("/schedule", schedulesRoute)
const HBCformRoute = require("./routes/hbcform");
app.use("/hbcform", HBCformRoute)
db.sequelize
.sync({ alter: true })
.then(() => {

42
server/models/HBCform.js Normal file
View File

@@ -0,0 +1,42 @@
const { DataTypes } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
const HBCform = sequelize.define(
"HBCform",
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true,
},
electricalBill: {
type: DataTypes.DECIMAL(7, 2),
allowNull: false
},
waterBill: {
type: DataTypes.DECIMAL(7, 2),
allowNull: false
},
totalBill: {
type: DataTypes.DECIMAL(7, 2),
allowNull: false
},
noOfDependents: {
type: DataTypes.INTEGER,
allowNull: false
},
ebPicture: {
type: DataTypes.BLOB("long"),
allowNull: true,
},
wbPicture: {
type: DataTypes.BLOB("long"),
allowNull: true,
},
},
{
tableName: 'hbcform'
});
return HBCform;
}

111
server/routes/hbcform.js Normal file
View File

@@ -0,0 +1,111 @@
const express = require('express');
const router = express.Router();
const { HBCform } = require('../models');
const { Op } = require("sequelize");
const yup = require("yup");
const multer = require("multer");
const sharp = require("sharp");
const upload = multer({ storage: multer.memoryStorage() });
router.post("/", upload.fields([
{ name: 'ebPicture', maxCount: 1 },
{ name: 'wbPicture', maxCount: 1 }
]), async (req, res) => {
console.log("Request Body:", req.body);
console.log("Request Files:", req.files);
let data = req.body;
let files = req.files;
// Validate request body
let validationSchema = yup.object({
electricalBill: yup.number().positive().required(),
waterBill: yup.number().positive().required(),
totalBill: yup.number().positive().required(),
noOfDependents: yup.number().integer().positive().required(),
});
try {
data = await validationSchema.validate(data, { abortEarly: false });
console.log("Validated data:", data);
// Process valid data
let ebPicture = files.ebPicture[0].buffer;
let wbPicture = files.wbPicture[0].buffer;
let result = await HBCform.create({ ...data, ebPicture, wbPicture });
res.json(result);
} catch (err) {
console.error("Error:", err);
res.status(400).json({ errors: err.errors });
}
});
router.get("/", async (req, res) => {
let condition = {};
let search = req.query.search;
if (search) {
condition[Op.or] = [
{ electricalBill: { [Op.like]: `%${search}%` } },
{ waterBill: { [Op.like]: `%${search}%` } },
{ totalBill: { [Op.like]: `%${search}%` } },
{ noOfDependents: { [Op.like]: `%${search}%` } },
];
}
let list = await HBCform.findAll({
where: condition,
order: [['createdAt', 'ASC']]
});
res.json(list);
});
router.get("/:id", async (req, res) => {
let id = req.params.id;
let hbcform = await HBCform.findByPk(id);
if (!hbcform) {
res.sendStatus(404);
return;
}
res.json(hbcform);
});
router.get("/ebPicture/:id", async (req, res) => {
let id = req.params.id;
let hbcform = await HBCform.findByPk(id);
if (!hbcform || !hbcform.ebPicture) {
res.sendStatus(404);
return;
}
try {
res.set("Content-Type", "image/jpeg"); // Adjust the content type as necessary
res.send(hbcform.ebPicture);
} catch (err) {
res
.status(500)
.json({ message: "Error retrieving electrical bill", error: err });
}
});
router.get("/wbPicture/:id", async (req, res) => {
let id = req.params.id;
let hbcform = await HBCform.findByPk(id);
if (!hbcform || !hbcform.wbPicture) {
res.sendStatus(404);
return;
}
try {
res.set("Content-Type", "image/jpeg"); // Adjust the content type as necessary
res.send(hbcform.wbPicture);
} catch (err) {
res
.status(500)
.json({ message: "Error retrieving electrical bill", error: err });
}
});
module.exports = router;