From 22e778e907384e294aec81e65a9d8991df4c0cc8 Mon Sep 17 00:00:00 2001 From: ZacTohZY Date: Mon, 12 Aug 2024 04:21:54 +0800 Subject: [PATCH] Fixed ui design on admin pages --- client/src/components/NextUIFormikSelect2.tsx | 31 +++ client/src/icons.tsx | 22 +- client/src/pages/CreateSchedulePage.tsx | 130 ++++++------ client/src/pages/CreateVoucherPage.tsx | 13 +- client/src/pages/EditSchedulePage.tsx | 136 +++++++------ client/src/pages/EditVoucherPage.tsx | 28 ++- client/src/pages/ManageSchedulePage.tsx | 10 +- client/src/pages/ManageVoucherPage.tsx | 190 +++++++++--------- client/src/pages/Ranking.tsx | 119 ++++++----- 9 files changed, 387 insertions(+), 292 deletions(-) create mode 100644 client/src/components/NextUIFormikSelect2.tsx diff --git a/client/src/components/NextUIFormikSelect2.tsx b/client/src/components/NextUIFormikSelect2.tsx new file mode 100644 index 0000000..47d4f60 --- /dev/null +++ b/client/src/components/NextUIFormikSelect2.tsx @@ -0,0 +1,31 @@ +import { Select, SelectItem } from "@nextui-org/react"; +import { ChangeEvent } from "react"; + +interface SimpleSelectProps { + label: string; + placeholder: string; + options: Array<{ key: string; label: string }>; + onChange: (value: string) => void; +} + +const NextUIFormikSelect2 = ({ label, placeholder, options, onChange }: SimpleSelectProps) => { + const handleChange = (e: ChangeEvent) => { + onChange(e.target.value); + }; + + return ( + + ); +}; + +export default NextUIFormikSelect2; diff --git a/client/src/icons.tsx b/client/src/icons.tsx index 7bb3181..7bea6e9 100644 --- a/client/src/icons.tsx +++ b/client/src/icons.tsx @@ -660,7 +660,7 @@ export const VoucherIcon = () => { xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" - stroke-width="1.5" + strokeWidth="1.5" stroke="currentColor" className="size-6" > @@ -710,3 +710,23 @@ export const BookOpenIcon = () => { ); }; + +export const ImageIcon = () => { + return ( + + + + + ); +}; diff --git a/client/src/pages/CreateSchedulePage.tsx b/client/src/pages/CreateSchedulePage.tsx index 62c0978..d95694c 100644 --- a/client/src/pages/CreateSchedulePage.tsx +++ b/client/src/pages/CreateSchedulePage.tsx @@ -7,6 +7,7 @@ import NextUIFormikInput from "../components/NextUIFormikInput"; import { useNavigate } from "react-router-dom"; import instance from "../security/http"; import dayjs from "dayjs"; +import { ArrowUTurnLeftIcon } from "../icons"; // Validation schema const validationSchema = yup.object().shape({ @@ -73,68 +74,73 @@ export default function CreateSchedulePage() { }; return ( -
-
- -
-

Add New Schedule

+
+
+
+ +
+

Add New Schedule

+
-
- - {({ isValid, dirty }) => ( -
-
- - -
-
- - -
- - Up coming - On going - Ended - - - {/* Example of using isValid and dirty */} -

Form is {isValid ? 'valid' : 'invalid'}

-

Form has been {dirty ? 'touched' : 'not touched'}

-
- )} -
-
+ + {({ isValid, dirty }) => ( +
+
+ + + + +
+ + Up coming + On going + Ended + + +
+ )} +
+ + ) } \ No newline at end of file diff --git a/client/src/pages/CreateVoucherPage.tsx b/client/src/pages/CreateVoucherPage.tsx index 5e55104..b1826d7 100644 --- a/client/src/pages/CreateVoucherPage.tsx +++ b/client/src/pages/CreateVoucherPage.tsx @@ -6,6 +6,7 @@ import InsertPostImage from "../components/InsertPostImage"; import NextUIFormikInput from "../components/NextUIFormikInput"; import { NextUIFormikDatePicker } from "../components/NextUIFormikDatePicker"; import instance from "../security/http"; +import { ArrowUTurnLeftIcon } from "../icons"; // Validation schema const validationSchema = Yup.object({ @@ -75,11 +76,11 @@ export default function CreateVoucherPage() { }; return ( -
-
+
+
-
@@ -90,7 +91,7 @@ export default function CreateVoucherPage() { > {({ isValid, dirty, setFieldValue }) => (
-
+
{ setFieldValue("brandLogo", file); @@ -130,7 +131,7 @@ export default function CreateVoucherPage() { labelPlacement="inside" />
- diff --git a/client/src/pages/EditSchedulePage.tsx b/client/src/pages/EditSchedulePage.tsx index 259b857..0e930d1 100644 --- a/client/src/pages/EditSchedulePage.tsx +++ b/client/src/pages/EditSchedulePage.tsx @@ -8,6 +8,7 @@ import NextUIFormikInput from "../components/NextUIFormikInput"; import { useNavigate, useParams } from "react-router-dom"; import instance from "../security/http"; import dayjs from "dayjs"; +import { ArrowUTurnLeftIcon } from "../icons"; // Validation schema @@ -92,73 +93,76 @@ export default function EditSchedulePage() { }; return ( -
-
- -
-

Add New Schedule

+
+
+
+ + +

Update Schedule

-
- - {({ isValid, dirty }) => ( - -
- - -
-
- - -
-
- + {({ isValid, dirty }) => ( + +
+ + + + +
+
+ + Up coming + On going + Ended + +
+
-
- -
- {/* Example of using isValid and dirty */} -

Form is {isValid ? 'valid' : 'invalid'}

-

Form has been {dirty ? 'touched' : 'not touched'}

- - )} -
-
+ Update + + + )} + +
+
) } diff --git a/client/src/pages/EditVoucherPage.tsx b/client/src/pages/EditVoucherPage.tsx index 2edd8a6..7b23dd6 100644 --- a/client/src/pages/EditVoucherPage.tsx +++ b/client/src/pages/EditVoucherPage.tsx @@ -6,14 +6,22 @@ import NextUIFormikInput from "../components/NextUIFormikInput"; import { NextUIFormikDatePicker } from "../components/NextUIFormikDatePicker"; import instance from "../security/http"; import { useEffect, useState } from "react"; +import { ArrowUTurnLeftIcon } from "../icons"; // Validation schema const validationSchema = Yup.object({ - brand: Yup.string().trim().required("Brand name is required"), - description: Yup.string().trim().required("Description is required"), - expirationDate: Yup.date().required("Expiry date is required"), - quantityAvailable: Yup.number().typeError("Must be a number").positive("Must be a positive value").required("Quantity is required"), - code: Yup.string().trim().required("Code is required"), + brand: Yup.string().trim() + .required("Brand name is required"), + description: Yup.string().trim() + .required("Description is required"), + expirationDate: Yup.date() + .required("Expiry date is required"), + quantityAvailable: Yup.number() + .typeError("Must be a number") + .positive("Must be a positive value") + .required("Quantity is required"), + code: Yup.string().trim() + .required("Code is required"), }); export default function EditVoucherPage() { @@ -74,11 +82,11 @@ export default function EditVoucherPage() { }; return ( -
-
+
+
-
@@ -90,7 +98,7 @@ export default function EditVoucherPage() { > {({ isValid }) => (
-
+
-
-

Admin Karang Guni Schedule

+
+
+

Karang Guni Schedule

-
+
@@ -170,7 +170,7 @@ export default function ManageSchedulePage() { )} - + ) } diff --git a/client/src/pages/ManageVoucherPage.tsx b/client/src/pages/ManageVoucherPage.tsx index cb25bc0..e60aa54 100644 --- a/client/src/pages/ManageVoucherPage.tsx +++ b/client/src/pages/ManageVoucherPage.tsx @@ -88,106 +88,106 @@ export default function ManageVoucherPage() { }; return ( -
-
-
-

Manage Vouchers

-
- -
+
+
+

Manage Vouchers

+
+
-
-
- - Brand Logo - Brand - Description - Expiration Date - Quantity Available - Code - Actions - - - {voucherList.map((voucher) => ( - - - {brandLogoUrls[voucher.id] && ( - {voucher.brand} - )} - - {voucher.brand} - {voucher.description} - - {voucher.expirationDate.toLocaleDateString()} - - {voucher.quantityAvailable} - {voucher.code} - - - - - - ))} - -
-
- - {/* Confirmation Modal for Deleting */} - - - {(onClose) => ( - <> - - Delete Voucher - - -

Are you sure you want to delete this voucher?

-
- -
+
+ + + Brand Logo + Brand + Description + Expiration Date + Quantity Available + Code + Actions + + + {voucherList.map((voucher) => ( + + + {brandLogoUrls[voucher.id] && ( + {voucher.brand} + )} + + {voucher.brand} + {voucher.description} + + {voucher.expirationDate.toLocaleDateString()} + + {voucher.quantityAvailable} + {voucher.code} + + - - - )} - - - + + + ))} + +
+
+ + {/* Confirmation Modal for Deleting */} + + + {(onClose) => ( + <> + + Delete Voucher + + +

Are you sure you want to delete this voucher?

+
+ + + + + + )} +
+
); } diff --git a/client/src/pages/Ranking.tsx b/client/src/pages/Ranking.tsx index eab9ff7..80b9454 100644 --- a/client/src/pages/Ranking.tsx +++ b/client/src/pages/Ranking.tsx @@ -2,7 +2,8 @@ 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, TrashDeleteIcon } from '../icons'; +import { EmailIcon, ImageIcon, TrashDeleteIcon } from '../icons'; +import NextUIFormikSelect2 from '../components/NextUIFormikSelect2'; interface User { id: string; @@ -125,31 +126,15 @@ export default function Ranking() { // Sort form data based on descriptor const sortFormData = (list: FormData[], descriptor: SortDescriptor) => { - const { column, direction } = descriptor; + const { column } = descriptor; if (column === "avgBill") { - return [...list].sort((a, b) => - direction === "ascending" ? a.avgBill - b.avgBill : b.avgBill - a.avgBill - ); + return [...list].sort((a, b) => a.avgBill - b.avgBill); } return list; }; - const handleSort = () => { - const { direction } = sortDescriptor; - const newDirection = direction === "ascending" ? "descending" : "ascending"; - - setSortDescriptor({ column: "avgBill", direction: newDirection }); - }; - - const renderSortIndicator = () => { - if (sortDescriptor.column === "avgBill") { - return sortDescriptor.direction === "ascending" ? : ; - } - return null; - }; - const sortedFormData = sortFormData(filteredFormData, sortDescriptor); const combinedData: FormDataWithUser[] = sortedFormData.map((data) => { @@ -162,6 +147,14 @@ export default function Ranking() { }; }); + const [isImageModalOpen, setIsImageModalOpen] = useState(false); + const [modalImageUrl, setModalImageUrl] = useState(null); + + const handleImageClick = (imageUrl: string) => { + setModalImageUrl(imageUrl); + setIsImageModalOpen(true); + }; + const handleEmailClick = (email: string, name: string) => { setSelectedUser({ email, name }); setIsEmailModalOpen(true); @@ -241,58 +234,76 @@ export default function Ranking() { } }; + const handleTownCouncilChange = (value: string) => { + setSelectedTownCouncil(value); + }; return ( -
-
-

Form Data

- {top3Users.length > 0 && ( - - )} +
+
+
+

Home Bill Contest Form Data

+
+
+
+ {townCouncils.length > 0 && ( + ({ + key: townCouncil, + label: townCouncil, + }))} + onChange={handleTownCouncilChange} + /> + )} +
+
+ {top3Users.length > 0 && ( + + )} +
+
-
- {townCouncils.length > 0 && ( - - )} + +
User Name - User Email Electrical Bill Water Bill Total Bill Dependents - - Average Bill {renderSortIndicator()} + + Average Bill Bill Picture Actions + {combinedData.map((data) => ( {data.userName} - {data.userEmail} ${data.electricalBill.toFixed(2)} ${data.waterBill.toFixed(2)} ${data.totalBill.toFixed(2)} {data.noOfDependents} ${data.avgBill.toFixed(2)} - {data.billPicture && bill picture} + {data.billPicture ? ( + + ) : ( + + )} + @@ -344,6 +355,20 @@ export default function Ranking() { )} - + {/* Open Image Modal */} + + + + {modalImageUrl && ( + Bill Picture + )} + + + + ); }