Fixed ui design on admin pages
This commit is contained in:
31
client/src/components/NextUIFormikSelect2.tsx
Normal file
31
client/src/components/NextUIFormikSelect2.tsx
Normal file
@@ -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<HTMLSelectElement>) => {
|
||||||
|
onChange(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
aria-label={label}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{options.map((option) => (
|
||||||
|
<SelectItem key={option.key} value={option.key}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NextUIFormikSelect2;
|
||||||
@@ -660,7 +660,7 @@ export const VoucherIcon = () => {
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
strokeWidth="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="size-6"
|
className="size-6"
|
||||||
>
|
>
|
||||||
@@ -710,3 +710,23 @@ export const BookOpenIcon = () => {
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ImageIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import NextUIFormikInput from "../components/NextUIFormikInput";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import instance from "../security/http";
|
import instance from "../security/http";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { ArrowUTurnLeftIcon } from "../icons";
|
||||||
|
|
||||||
// Validation schema
|
// Validation schema
|
||||||
const validationSchema = yup.object().shape({
|
const validationSchema = yup.object().shape({
|
||||||
@@ -73,15 +74,17 @@ export default function CreateSchedulePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex flex-col items-center justify-center gap-20 py-8 md:py-10">
|
<div className="w-full h-full pb-12 pt-10">
|
||||||
<div className="w-full flex items-start">
|
<div className="w-[400px] mx-auto p-6 bg-red-50 dark:bg-primary-950 border border-primary-100 rounded-2xl">
|
||||||
|
<div>
|
||||||
<Button
|
<Button
|
||||||
|
isIconOnly
|
||||||
variant="light"
|
variant="light"
|
||||||
onPress={() => navigate(-1)}
|
onPress={() => navigate(-1)}
|
||||||
>
|
>
|
||||||
Back
|
<ArrowUTurnLeftIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex-grow text-center">
|
<div className="pt-2">
|
||||||
<p className="text-3xl font-bold">Add New Schedule</p>
|
<p className="text-3xl font-bold">Add New Schedule</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,12 +94,12 @@ export default function CreateSchedulePage() {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
{({ isValid, dirty }) => (
|
{({ isValid, dirty }) => (
|
||||||
<Form className="flex flex-col gap-4">
|
<Form className="flex flex-col gap-4 pt-5 items-center justify-center">
|
||||||
<div className="flex gap-8">
|
<div className="flex flex-col gap-5 w-[360px]">
|
||||||
<NextUIFormikDatePicker
|
<NextUIFormikDatePicker
|
||||||
label="Date"
|
label="Date"
|
||||||
name="date"
|
name="date"
|
||||||
className="max-w-[280px]"
|
className="max-w-[360px]"
|
||||||
/>
|
/>
|
||||||
<NextUIFormikInput
|
<NextUIFormikInput
|
||||||
type='time'
|
type='time'
|
||||||
@@ -104,8 +107,6 @@ export default function CreateSchedulePage() {
|
|||||||
name="time"
|
name="time"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="flex gap-8">
|
|
||||||
<NextUIFormikInput
|
<NextUIFormikInput
|
||||||
type="text"
|
type="text"
|
||||||
label="Location"
|
label="Location"
|
||||||
@@ -128,13 +129,18 @@ export default function CreateSchedulePage() {
|
|||||||
<Radio value="On going">On going</Radio>
|
<Radio value="On going">On going</Radio>
|
||||||
<Radio value="Ended">Ended</Radio>
|
<Radio value="Ended">Ended</Radio>
|
||||||
</NextUIFormikRadioGroup>
|
</NextUIFormikRadioGroup>
|
||||||
<Button type="submit" color="primary" className="w-[100px]">Create</Button>
|
<Button
|
||||||
{/* Example of using isValid and dirty */}
|
type="submit"
|
||||||
<p>Form is {isValid ? 'valid' : 'invalid'}</p>
|
color="primary"
|
||||||
<p>Form has been {dirty ? 'touched' : 'not touched'}</p>
|
className="w-[100px]"
|
||||||
|
isDisabled={!isValid || !dirty}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</section >
|
</div >
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import InsertPostImage from "../components/InsertPostImage";
|
|||||||
import NextUIFormikInput from "../components/NextUIFormikInput";
|
import NextUIFormikInput from "../components/NextUIFormikInput";
|
||||||
import { NextUIFormikDatePicker } from "../components/NextUIFormikDatePicker";
|
import { NextUIFormikDatePicker } from "../components/NextUIFormikDatePicker";
|
||||||
import instance from "../security/http";
|
import instance from "../security/http";
|
||||||
|
import { ArrowUTurnLeftIcon } from "../icons";
|
||||||
|
|
||||||
// Validation schema
|
// Validation schema
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -75,11 +76,11 @@ export default function CreateVoucherPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full pb-12">
|
<div className="w-full h-full pb-12 pt-10">
|
||||||
<div className="w-[680px] mx-auto p-6 bg-gray-100 dark:bg-gray-950 border border-primary-100 rounded-2xl">
|
<div className="w-[350px] mx-auto p-6 bg-red-50 dark:bg-primary-950 border border-primary-100 rounded-2xl ">
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<Button variant="light" onPress={() => navigate(-1)}>
|
<Button variant="light" isIconOnly onPress={() => navigate(-1)}>
|
||||||
Back
|
<ArrowUTurnLeftIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow overflow-y-auto">
|
<div className="flex-grow overflow-y-auto">
|
||||||
@@ -90,7 +91,7 @@ export default function CreateVoucherPage() {
|
|||||||
>
|
>
|
||||||
{({ isValid, dirty, setFieldValue }) => (
|
{({ isValid, dirty, setFieldValue }) => (
|
||||||
<Form className="flex flex-col gap-4">
|
<Form className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-5 w-[280px]">
|
||||||
<InsertPostImage
|
<InsertPostImage
|
||||||
onImageSelected={(file) => {
|
onImageSelected={(file) => {
|
||||||
setFieldValue("brandLogo", file);
|
setFieldValue("brandLogo", file);
|
||||||
@@ -130,7 +131,7 @@ export default function CreateVoucherPage() {
|
|||||||
labelPlacement="inside"
|
labelPlacement="inside"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" color="primary" className="w-[100px]"
|
<Button type="submit" color="primary" className="w-[90px]"
|
||||||
isDisabled={!isValid || !dirty}>
|
isDisabled={!isValid || !dirty}>
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import NextUIFormikInput from "../components/NextUIFormikInput";
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import instance from "../security/http";
|
import instance from "../security/http";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { ArrowUTurnLeftIcon } from "../icons";
|
||||||
|
|
||||||
|
|
||||||
// Validation schema
|
// Validation schema
|
||||||
@@ -92,18 +93,20 @@ export default function EditSchedulePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex flex-col items-center justify-center gap-20 py-8 md:py-10">
|
<div className="w-full h-full pb-12 pt-10">
|
||||||
<div className="w-full flex items-start">
|
<div className="w-[400px] mx-auto p-6 bg-red-50 dark:bg-primary-950 border border-primary-100 rounded-2xl">
|
||||||
|
<div>
|
||||||
<Button
|
<Button
|
||||||
|
isIconOnly
|
||||||
variant="light"
|
variant="light"
|
||||||
onPress={() => navigate(-1)}
|
onPress={() => navigate(-1)}
|
||||||
>
|
>
|
||||||
Back
|
<ArrowUTurnLeftIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex-grow text-center">
|
|
||||||
<p className="text-3xl font-bold">Add New Schedule</p>
|
<p className="text-3xl font-bold pt-2">Update Schedule</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Formik
|
<Formik
|
||||||
enableReinitialize
|
enableReinitialize
|
||||||
initialValues={schedule}
|
initialValues={schedule}
|
||||||
@@ -111,12 +114,12 @@ export default function EditSchedulePage() {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
{({ isValid, dirty }) => (
|
{({ isValid, dirty }) => (
|
||||||
<Form className="flex flex-col gap-4">
|
<Form className="flex flex-col gap-4 pt-5 items-center justify-center">
|
||||||
<div className="flex gap-8">
|
<div className="flex flex-col gap-5 w-[360px]">
|
||||||
<NextUIFormikDatePicker
|
<NextUIFormikDatePicker
|
||||||
label="Date"
|
label="Date"
|
||||||
name="date"
|
name="date"
|
||||||
className="max-w-[284px]"
|
className="max-w-[360px]"
|
||||||
/>
|
/>
|
||||||
<NextUIFormikInput
|
<NextUIFormikInput
|
||||||
type='time'
|
type='time'
|
||||||
@@ -124,8 +127,6 @@ export default function EditSchedulePage() {
|
|||||||
name="time"
|
name="time"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="flex gap-8">
|
|
||||||
<NextUIFormikInput
|
<NextUIFormikInput
|
||||||
type="text"
|
type="text"
|
||||||
label="Location"
|
label="Location"
|
||||||
@@ -150,15 +151,18 @@ export default function EditSchedulePage() {
|
|||||||
<Radio value="Ended">Ended</Radio>
|
<Radio value="Ended">Ended</Radio>
|
||||||
</NextUIFormikRadioGroup>
|
</NextUIFormikRadioGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-5">
|
<Button
|
||||||
<Button type="submit" color="secondary" className="max-w-[100px]">Update</Button>
|
type="submit"
|
||||||
</div>
|
color="primary"
|
||||||
{/* Example of using isValid and dirty */}
|
className="w-[100px]"
|
||||||
<p>Form is {isValid ? 'valid' : 'invalid'}</p>
|
isDisabled={!isValid || !dirty}
|
||||||
<p>Form has been {dirty ? 'touched' : 'not touched'}</p>
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</section>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,22 @@ import NextUIFormikInput from "../components/NextUIFormikInput";
|
|||||||
import { NextUIFormikDatePicker } from "../components/NextUIFormikDatePicker";
|
import { NextUIFormikDatePicker } from "../components/NextUIFormikDatePicker";
|
||||||
import instance from "../security/http";
|
import instance from "../security/http";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { ArrowUTurnLeftIcon } from "../icons";
|
||||||
|
|
||||||
// Validation schema
|
// Validation schema
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
brand: Yup.string().trim().required("Brand name is required"),
|
brand: Yup.string().trim()
|
||||||
description: Yup.string().trim().required("Description is required"),
|
.required("Brand name is required"),
|
||||||
expirationDate: Yup.date().required("Expiry date is required"),
|
description: Yup.string().trim()
|
||||||
quantityAvailable: Yup.number().typeError("Must be a number").positive("Must be a positive value").required("Quantity is required"),
|
.required("Description is required"),
|
||||||
code: Yup.string().trim().required("Code 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() {
|
export default function EditVoucherPage() {
|
||||||
@@ -74,11 +82,11 @@ export default function EditVoucherPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full pb-12">
|
<div className="w-full h-full pb-12 pt-20">
|
||||||
<div className="w-[680px] mx-auto p-6 bg-gray-100 dark:bg-gray-950 border border-primary-100 rounded-2xl">
|
<div className="w-[350px] mx-auto p-6 bg-red-50 dark:bg-primary-950 border border-primary-100 rounded-2xl">
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<Button variant="light" onPress={() => navigate(-1)}>
|
<Button variant="light" isIconOnly onPress={() => navigate(-1)}>
|
||||||
Back
|
<ArrowUTurnLeftIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow overflow-y-auto">
|
<div className="flex-grow overflow-y-auto">
|
||||||
@@ -90,7 +98,7 @@ export default function EditVoucherPage() {
|
|||||||
>
|
>
|
||||||
{({ isValid }) => (
|
{({ isValid }) => (
|
||||||
<Form className="flex flex-col gap-4">
|
<Form className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4 w-[280px]">
|
||||||
<NextUIFormikInput
|
<NextUIFormikInput
|
||||||
label="Brand name"
|
label="Brand name"
|
||||||
name="brand"
|
name="brand"
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ export default function ManageSchedulePage() {
|
|||||||
const sortedScheduleList = sortScheduleList(scheduleList, sortDescriptor);
|
const sortedScheduleList = sortScheduleList(scheduleList, sortDescriptor);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
|
<div className="flex flex-col gap-8 p-8">
|
||||||
<div className="inline-block text-center justify-center flex flex-row gap-10">
|
<div className="inline-block text-center justify-between flex flex-row gap-10">
|
||||||
<p className="text-3xl font-bold">Admin Karang Guni Schedule</p>
|
<p className="text-4xl font-bold">Karang Guni Schedule</p>
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -108,7 +108,7 @@ export default function ManageSchedulePage() {
|
|||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full overflow-auto max-w-screen-lg">
|
<div>
|
||||||
<Table aria-label="Schedule Table">
|
<Table aria-label="Schedule Table">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableColumn>
|
<TableColumn>
|
||||||
@@ -170,7 +170,7 @@ export default function ManageSchedulePage() {
|
|||||||
)}
|
)}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
</section>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,14 +88,15 @@ export default function ManageVoucherPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full">
|
<div className="flex flex-col gap-8 p-8">
|
||||||
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
|
<div className="flex flex-row justify-between gap-10">
|
||||||
<div className="flex flex-row gap-10">
|
<p className="text-4xl font-bold">Manage Vouchers</p>
|
||||||
<p className="text-2xl font-bold">Manage Vouchers</p>
|
<div className="flex justify-end">
|
||||||
<div>
|
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
onPress={() => navigate("create-voucher")}
|
onPress={() => navigate("create-voucher")}
|
||||||
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -120,7 +121,7 @@ export default function ManageVoucherPage() {
|
|||||||
<img
|
<img
|
||||||
src={brandLogoUrls[voucher.id]}
|
src={brandLogoUrls[voucher.id]}
|
||||||
alt={voucher.brand}
|
alt={voucher.brand}
|
||||||
style={{ width: "50px", height: "50px" }}
|
style={{ width: "40px", height: "40px" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -187,7 +188,6 @@ export default function ManageVoucherPage() {
|
|||||||
)}
|
)}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { useEffect, useState } from 'react';
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import instance from '../security/http';
|
import instance from '../security/http';
|
||||||
import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, SortDescriptor, Button, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react';
|
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 {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -125,31 +126,15 @@ export default function Ranking() {
|
|||||||
|
|
||||||
// Sort form data based on descriptor
|
// Sort form data based on descriptor
|
||||||
const sortFormData = (list: FormData[], descriptor: SortDescriptor) => {
|
const sortFormData = (list: FormData[], descriptor: SortDescriptor) => {
|
||||||
const { column, direction } = descriptor;
|
const { column } = descriptor;
|
||||||
|
|
||||||
if (column === "avgBill") {
|
if (column === "avgBill") {
|
||||||
return [...list].sort((a, b) =>
|
return [...list].sort((a, b) => a.avgBill - b.avgBill);
|
||||||
direction === "ascending" ? a.avgBill - b.avgBill : b.avgBill - a.avgBill
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
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" ? <span>↑</span> : <span>↓</span>;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortedFormData = sortFormData(filteredFormData, sortDescriptor);
|
const sortedFormData = sortFormData(filteredFormData, sortDescriptor);
|
||||||
|
|
||||||
const combinedData: FormDataWithUser[] = sortedFormData.map((data) => {
|
const combinedData: FormDataWithUser[] = sortedFormData.map((data) => {
|
||||||
@@ -162,6 +147,14 @@ export default function Ranking() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [isImageModalOpen, setIsImageModalOpen] = useState(false);
|
||||||
|
const [modalImageUrl, setModalImageUrl] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleImageClick = (imageUrl: string) => {
|
||||||
|
setModalImageUrl(imageUrl);
|
||||||
|
setIsImageModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const handleEmailClick = (email: string, name: string) => {
|
const handleEmailClick = (email: string, name: string) => {
|
||||||
setSelectedUser({ email, name });
|
setSelectedUser({ email, name });
|
||||||
setIsEmailModalOpen(true);
|
setIsEmailModalOpen(true);
|
||||||
@@ -241,58 +234,76 @@ export default function Ranking() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTownCouncilChange = (value: string) => {
|
||||||
|
setSelectedTownCouncil(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex flex-col items-center justify-center py-5">
|
<div className="flex flex-col gap-8 p-8">
|
||||||
<div className="flex justify-between w-full">
|
<div className="flex justify-between items-center gap-5">
|
||||||
<p className="text-xl font-bold">Form Data</p>
|
<div className="flex w-[500px]">
|
||||||
|
<p className="text-4xl font-bold">Home Bill Contest Form Data</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-4 ">
|
||||||
|
<div className="w-[200px]">
|
||||||
|
{townCouncils.length > 0 && (
|
||||||
|
<NextUIFormikSelect2
|
||||||
|
label="Town council"
|
||||||
|
placeholder="Choose towncouncil"
|
||||||
|
options={townCouncils.map((townCouncil) => ({
|
||||||
|
key: townCouncil,
|
||||||
|
label: townCouncil,
|
||||||
|
}))}
|
||||||
|
onChange={handleTownCouncilChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="w-[130px]">
|
||||||
{top3Users.length > 0 && (
|
{top3Users.length > 0 && (
|
||||||
<Button color="primary" onPress={handleGiveVouchers}>
|
<Button color="primary" onPress={handleGiveVouchers} className="w-full">
|
||||||
Give Vouchers
|
Give Vouchers
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="gap-8 p-8">
|
</div>
|
||||||
{townCouncils.length > 0 && (
|
</div>
|
||||||
<select
|
|
||||||
value={selectedTownCouncil}
|
<div>
|
||||||
onChange={(e) => setSelectedTownCouncil(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">All locations</option>
|
|
||||||
{townCouncils.map((townCouncil) => (
|
|
||||||
<option key={townCouncil} value={townCouncil}>
|
|
||||||
{townCouncil}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)}
|
|
||||||
<Table aria-label="Form Data Table">
|
<Table aria-label="Form Data Table">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableColumn>User Name</TableColumn>
|
<TableColumn>User Name</TableColumn>
|
||||||
<TableColumn>User Email</TableColumn>
|
|
||||||
<TableColumn>Electrical Bill</TableColumn>
|
<TableColumn>Electrical Bill</TableColumn>
|
||||||
<TableColumn>Water Bill</TableColumn>
|
<TableColumn>Water Bill</TableColumn>
|
||||||
<TableColumn>Total Bill</TableColumn>
|
<TableColumn>Total Bill</TableColumn>
|
||||||
<TableColumn>Dependents</TableColumn>
|
<TableColumn>Dependents</TableColumn>
|
||||||
<TableColumn onClick={handleSort}>
|
<TableColumn>
|
||||||
Average Bill {renderSortIndicator()}
|
Average Bill
|
||||||
</TableColumn>
|
</TableColumn>
|
||||||
<TableColumn>Bill Picture</TableColumn>
|
<TableColumn>Bill Picture</TableColumn>
|
||||||
<TableColumn>Actions</TableColumn>
|
<TableColumn>Actions</TableColumn>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{combinedData.map((data) => (
|
{combinedData.map((data) => (
|
||||||
<TableRow key={data.id}>
|
<TableRow key={data.id}>
|
||||||
<TableCell>{data.userName}</TableCell>
|
<TableCell>{data.userName}</TableCell>
|
||||||
<TableCell>{data.userEmail}</TableCell>
|
|
||||||
<TableCell>${data.electricalBill.toFixed(2)}</TableCell>
|
<TableCell>${data.electricalBill.toFixed(2)}</TableCell>
|
||||||
<TableCell>${data.waterBill.toFixed(2)}</TableCell>
|
<TableCell>${data.waterBill.toFixed(2)}</TableCell>
|
||||||
<TableCell>${data.totalBill.toFixed(2)}</TableCell>
|
<TableCell>${data.totalBill.toFixed(2)}</TableCell>
|
||||||
<TableCell>{data.noOfDependents}</TableCell>
|
<TableCell>{data.noOfDependents}</TableCell>
|
||||||
<TableCell>${data.avgBill.toFixed(2)}</TableCell>
|
<TableCell>${data.avgBill.toFixed(2)}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{data.billPicture && <img src={`${config.serverAddress}/hbcform/billPicture/${data.id}`} alt="bill picture" className="w-full" />}
|
{data.billPicture ? (
|
||||||
|
<Button isIconOnly variant="light" onPress={() => handleImageClick(`${config.serverAddress}/hbcform/billPicture/${data.id}`)}>
|
||||||
|
<ImageIcon />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button isIconOnly variant="light">
|
||||||
|
<ImageIcon />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="flex flex-row">
|
<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" 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" color="danger" onClick={() => handleDeleteClick(data.id)}><TrashDeleteIcon /></Button>
|
||||||
@@ -344,6 +355,20 @@ export default function Ranking() {
|
|||||||
)}
|
)}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
</section>
|
{/* Open Image Modal */}
|
||||||
|
<Modal
|
||||||
|
isOpen={isImageModalOpen}
|
||||||
|
onOpenChange={setIsImageModalOpen}
|
||||||
|
isDismissable={true}
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalBody>
|
||||||
|
{modalImageUrl && (
|
||||||
|
<img src={modalImageUrl} alt="Bill Picture" style={{ width: '100%', height: 'auto' }} />
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</div >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user