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"
|
||||
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 = () => {
|
||||
</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 instance from "../security/http";
|
||||
import dayjs from "dayjs";
|
||||
import { ArrowUTurnLeftIcon } from "../icons";
|
||||
|
||||
// Validation schema
|
||||
const validationSchema = yup.object().shape({
|
||||
@@ -73,15 +74,17 @@ export default function CreateSchedulePage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="flex flex-col items-center justify-center gap-20 py-8 md:py-10">
|
||||
<div className="w-full flex items-start">
|
||||
<div className="w-full h-full pb-12 pt-10">
|
||||
<div className="w-[400px] mx-auto p-6 bg-red-50 dark:bg-primary-950 border border-primary-100 rounded-2xl">
|
||||
<div>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={() => navigate(-1)}
|
||||
>
|
||||
Back
|
||||
<ArrowUTurnLeftIcon />
|
||||
</Button>
|
||||
<div className="flex-grow text-center">
|
||||
<div className="pt-2">
|
||||
<p className="text-3xl font-bold">Add New Schedule</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,12 +94,12 @@ export default function CreateSchedulePage() {
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ isValid, dirty }) => (
|
||||
<Form className="flex flex-col gap-4">
|
||||
<div className="flex gap-8">
|
||||
<Form className="flex flex-col gap-4 pt-5 items-center justify-center">
|
||||
<div className="flex flex-col gap-5 w-[360px]">
|
||||
<NextUIFormikDatePicker
|
||||
label="Date"
|
||||
name="date"
|
||||
className="max-w-[280px]"
|
||||
className="max-w-[360px]"
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
type='time'
|
||||
@@ -104,8 +107,6 @@ export default function CreateSchedulePage() {
|
||||
name="time"
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-8">
|
||||
<NextUIFormikInput
|
||||
type="text"
|
||||
label="Location"
|
||||
@@ -128,13 +129,18 @@ export default function CreateSchedulePage() {
|
||||
<Radio value="On going">On going</Radio>
|
||||
<Radio value="Ended">Ended</Radio>
|
||||
</NextUIFormikRadioGroup>
|
||||
<Button type="submit" color="primary" className="w-[100px]">Create</Button>
|
||||
{/* Example of using isValid and dirty */}
|
||||
<p>Form is {isValid ? 'valid' : 'invalid'}</p>
|
||||
<p>Form has been {dirty ? 'touched' : 'not touched'}</p>
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
className="w-[100px]"
|
||||
isDisabled={!isValid || !dirty}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</section >
|
||||
</div >
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="w-full h-full pb-12">
|
||||
<div className="w-[680px] mx-auto p-6 bg-gray-100 dark:bg-gray-950 border border-primary-100 rounded-2xl">
|
||||
<div className="w-full h-full pb-12 pt-10">
|
||||
<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">
|
||||
<Button variant="light" onPress={() => navigate(-1)}>
|
||||
Back
|
||||
<Button variant="light" isIconOnly onPress={() => navigate(-1)}>
|
||||
<ArrowUTurnLeftIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-grow overflow-y-auto">
|
||||
@@ -90,7 +91,7 @@ export default function CreateVoucherPage() {
|
||||
>
|
||||
{({ isValid, dirty, setFieldValue }) => (
|
||||
<Form className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-5 w-[280px]">
|
||||
<InsertPostImage
|
||||
onImageSelected={(file) => {
|
||||
setFieldValue("brandLogo", file);
|
||||
@@ -130,7 +131,7 @@ export default function CreateVoucherPage() {
|
||||
labelPlacement="inside"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" color="primary" className="w-[100px]"
|
||||
<Button type="submit" color="primary" className="w-[90px]"
|
||||
isDisabled={!isValid || !dirty}>
|
||||
Create
|
||||
</Button>
|
||||
|
||||
@@ -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,18 +93,20 @@ export default function EditSchedulePage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="flex flex-col items-center justify-center gap-20 py-8 md:py-10">
|
||||
<div className="w-full flex items-start">
|
||||
<div className="w-full h-full pb-12 pt-10">
|
||||
<div className="w-[400px] mx-auto p-6 bg-red-50 dark:bg-primary-950 border border-primary-100 rounded-2xl">
|
||||
<div>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={() => navigate(-1)}
|
||||
>
|
||||
Back
|
||||
<ArrowUTurnLeftIcon />
|
||||
</Button>
|
||||
<div className="flex-grow text-center">
|
||||
<p className="text-3xl font-bold">Add New Schedule</p>
|
||||
</div>
|
||||
|
||||
<p className="text-3xl font-bold pt-2">Update Schedule</p>
|
||||
</div>
|
||||
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={schedule}
|
||||
@@ -111,12 +114,12 @@ export default function EditSchedulePage() {
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ isValid, dirty }) => (
|
||||
<Form className="flex flex-col gap-4">
|
||||
<div className="flex gap-8">
|
||||
<Form className="flex flex-col gap-4 pt-5 items-center justify-center">
|
||||
<div className="flex flex-col gap-5 w-[360px]">
|
||||
<NextUIFormikDatePicker
|
||||
label="Date"
|
||||
name="date"
|
||||
className="max-w-[284px]"
|
||||
className="max-w-[360px]"
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
type='time'
|
||||
@@ -124,8 +127,6 @@ export default function EditSchedulePage() {
|
||||
name="time"
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-8">
|
||||
<NextUIFormikInput
|
||||
type="text"
|
||||
label="Location"
|
||||
@@ -150,15 +151,18 @@ export default function EditSchedulePage() {
|
||||
<Radio value="Ended">Ended</Radio>
|
||||
</NextUIFormikRadioGroup>
|
||||
</div>
|
||||
<div className="flex gap-5">
|
||||
<Button type="submit" color="secondary" className="max-w-[100px]">Update</Button>
|
||||
</div>
|
||||
{/* Example of using isValid and dirty */}
|
||||
<p>Form is {isValid ? 'valid' : 'invalid'}</p>
|
||||
<p>Form has been {dirty ? 'touched' : 'not touched'}</p>
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
className="w-[100px]"
|
||||
isDisabled={!isValid || !dirty}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="w-full h-full pb-12">
|
||||
<div className="w-[680px] mx-auto p-6 bg-gray-100 dark:bg-gray-950 border border-primary-100 rounded-2xl">
|
||||
<div className="w-full h-full pb-12 pt-20">
|
||||
<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">
|
||||
<Button variant="light" onPress={() => navigate(-1)}>
|
||||
Back
|
||||
<Button variant="light" isIconOnly onPress={() => navigate(-1)}>
|
||||
<ArrowUTurnLeftIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-grow overflow-y-auto">
|
||||
@@ -90,7 +98,7 @@ export default function EditVoucherPage() {
|
||||
>
|
||||
{({ isValid }) => (
|
||||
<Form className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 w-[280px]">
|
||||
<NextUIFormikInput
|
||||
label="Brand name"
|
||||
name="brand"
|
||||
|
||||
@@ -97,9 +97,9 @@ export default function ManageSchedulePage() {
|
||||
const sortedScheduleList = sortScheduleList(scheduleList, sortDescriptor);
|
||||
|
||||
return (
|
||||
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
|
||||
<div className="inline-block text-center justify-center flex flex-row gap-10">
|
||||
<p className="text-3xl font-bold">Admin Karang Guni Schedule</p>
|
||||
<div className="flex flex-col gap-8 p-8">
|
||||
<div className="inline-block text-center justify-between flex flex-row gap-10">
|
||||
<p className="text-4xl font-bold">Karang Guni Schedule</p>
|
||||
<Button
|
||||
isIconOnly
|
||||
color="primary"
|
||||
@@ -108,7 +108,7 @@ export default function ManageSchedulePage() {
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-full overflow-auto max-w-screen-lg">
|
||||
<div>
|
||||
<Table aria-label="Schedule Table">
|
||||
<TableHeader>
|
||||
<TableColumn>
|
||||
@@ -170,7 +170,7 @@ export default function ManageSchedulePage() {
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -88,14 +88,15 @@ export default function ManageVoucherPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
|
||||
<div className="flex flex-row gap-10">
|
||||
<p className="text-2xl font-bold">Manage Vouchers</p>
|
||||
<div>
|
||||
<div className="flex flex-col gap-8 p-8">
|
||||
<div className="flex flex-row justify-between gap-10">
|
||||
<p className="text-4xl font-bold">Manage Vouchers</p>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
isIconOnly
|
||||
onPress={() => navigate("create-voucher")}
|
||||
color="primary"
|
||||
variant="solid"
|
||||
>
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
@@ -120,7 +121,7 @@ export default function ManageVoucherPage() {
|
||||
<img
|
||||
src={brandLogoUrls[voucher.id]}
|
||||
alt={voucher.brand}
|
||||
style={{ width: "50px", height: "50px" }}
|
||||
style={{ width: "40px", height: "40px" }}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
@@ -187,7 +188,6 @@ export default function ManageVoucherPage() {
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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" ? <span>↑</span> : <span>↓</span>;
|
||||
}
|
||||
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<string | null>(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 (
|
||||
<section className="flex flex-col items-center justify-center py-5">
|
||||
<div className="flex justify-between w-full">
|
||||
<p className="text-xl font-bold">Form Data</p>
|
||||
<div className="flex flex-col gap-8 p-8">
|
||||
<div className="flex justify-between items-center gap-5">
|
||||
<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 && (
|
||||
<Button color="primary" onPress={handleGiveVouchers}>
|
||||
<Button color="primary" onPress={handleGiveVouchers} className="w-full">
|
||||
Give Vouchers
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="gap-8 p-8">
|
||||
{townCouncils.length > 0 && (
|
||||
<select
|
||||
value={selectedTownCouncil}
|
||||
onChange={(e) => setSelectedTownCouncil(e.target.value)}
|
||||
>
|
||||
<option value="">All locations</option>
|
||||
{townCouncils.map((townCouncil) => (
|
||||
<option key={townCouncil} value={townCouncil}>
|
||||
{townCouncil}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Table aria-label="Form Data Table">
|
||||
<TableHeader>
|
||||
<TableColumn>User Name</TableColumn>
|
||||
<TableColumn>User Email</TableColumn>
|
||||
<TableColumn>Electrical Bill</TableColumn>
|
||||
<TableColumn>Water Bill</TableColumn>
|
||||
<TableColumn>Total Bill</TableColumn>
|
||||
<TableColumn>Dependents</TableColumn>
|
||||
<TableColumn onClick={handleSort}>
|
||||
Average Bill {renderSortIndicator()}
|
||||
<TableColumn>
|
||||
Average Bill
|
||||
</TableColumn>
|
||||
<TableColumn>Bill Picture</TableColumn>
|
||||
<TableColumn>Actions</TableColumn>
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{combinedData.map((data) => (
|
||||
<TableRow key={data.id}>
|
||||
<TableCell>{data.userName}</TableCell>
|
||||
<TableCell>{data.userEmail}</TableCell>
|
||||
<TableCell>${data.electricalBill.toFixed(2)}</TableCell>
|
||||
<TableCell>${data.waterBill.toFixed(2)}</TableCell>
|
||||
<TableCell>${data.totalBill.toFixed(2)}</TableCell>
|
||||
<TableCell>{data.noOfDependents}</TableCell>
|
||||
<TableCell>${data.avgBill.toFixed(2)}</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 className="flex flex-row">
|
||||
<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>
|
||||
@@ -344,6 +355,20 @@ export default function Ranking() {
|
||||
)}
|
||||
</ModalContent>
|
||||
</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