Fixed Function of Admin KG Schedule

This commit is contained in:
ZacTohZY
2024-07-31 15:00:06 +08:00
parent 092795d8e8
commit 4d0a814b73
9 changed files with 3907 additions and 2848 deletions

View File

@@ -10,8 +10,10 @@
"preview": "vite preview"
},
"dependencies": {
"@internationalized/date": "^3.5.5",
"@nextui-org/react": "^2.4.2",
"axios": "^1.7.2",
"dayjs": "^1.11.12",
"formik": "^2.4.6",
"framer-motion": "^11.2.10",
"openai": "^4.53.2",

6170
client/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,8 @@ import ForgotPasswordPage from "./pages/ForgotPasswordPage";
import RestrictedLayout from "./layouts/restricted";
import Ranking from "./pages/Ranking";
import ManageSchedulePage from "./pages/ManageSchedulePage";
import EditSchedulePage from "./pages/EditSchedulePage";
import CreateSchedulePage from "./pages/CreateSchedulePage";
function App() {
return (
@@ -98,6 +100,8 @@ function App() {
<Route path="schedules">
<Route index element={<ManageSchedulePage />} />
<Route path="create-schedule" element={<CreateSchedulePage />} />
<Route path="edit-schedule/:id" element={<EditSchedulePage />} />
</Route>
</Route>
</Route>

View File

@@ -0,0 +1,42 @@
import { DatePicker } from "@nextui-org/react";
import { useField } from "formik";
import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";
interface NextUIFormikDatePickerProps {
label: string;
name: string;
className?: string;
}
const dateToCalendarDate = (date: Date): CalendarDate => {
return new CalendarDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
};
// NextUIFormikDatePicker component
export const NextUIFormikDatePicker = ({
label,
...props
}: NextUIFormikDatePickerProps) => {
const [field, meta, helpers] = useField(props.name);
const handleChange = (date: CalendarDate | null) => {
if (date) {
helpers.setValue(date.toDate('UTC')); // Ensure date is converted to a proper Date object with a timezone
} else {
helpers.setValue(null);
}
};
return (
<DatePicker
{...props}
label={label}
value={field.value ? dateToCalendarDate(new Date(field.value)) : null} // Convert field value to CalendarDate
onChange={handleChange}
isInvalid={meta.touched && !!meta.error}
errorMessage={meta.touched && meta.error ? meta.error : ""}
minValue={today(getLocalTimeZone())}
defaultValue={today(getLocalTimeZone()).subtract({ days: 1 })}
/>
);
};

View File

@@ -0,0 +1,34 @@
import { RadioGroup } from "@nextui-org/react";
import { useField } from "formik";
interface NextUIFormikRadioGroupProps {
label: string;
name: string;
className?: string;
children: React.ReactNode;
}
// NextUIFormikRadioGroup component
export const NextUIFormikRadioGroup = ({
label,
...props
}: NextUIFormikRadioGroupProps) => {
const [field, meta, helpers] = useField(props.name);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
helpers.setValue(event.target.value);
};
return (
<RadioGroup
{...props}
label={label}
value={field.value}
onChange={handleChange}
isInvalid={meta.touched && !!meta.error}
errorMessage={meta.touched && meta.error ? meta.error : ""}
>
{props.children}
</RadioGroup>
);
};

View File

@@ -0,0 +1,140 @@
import { Formik, Form } from "formik";
import * as yup from "yup";
import { Button, Radio } from "@nextui-org/react";
import { NextUIFormikRadioGroup } from "../components/NextUIFormikRadioButton";
import { NextUIFormikDatePicker } from "../components/NextUIFormikDatePicker";
import NextUIFormikInput from "../components/NextUIFormikInput";
import { useNavigate } from "react-router-dom";
import instance from "../security/http";
import dayjs from "dayjs";
// Validation schema
const validationSchema = yup.object().shape({
date: yup.date().required(),
time: yup.string().trim().required(),
location: yup.string().trim().min(15).max(50).required(),
postalCode: yup.string().matches(/^\d{6}$/, "Postal code must be exactly 6 digits").required(),
status: yup.string().trim().required()
});
const initialValues: any = {
date: "",
time: "",
location: "",
postalCode: "",
status: ""
};
export default function CreateSchedulePage() {
const navigate = useNavigate();
const handleSubmit = (values: any) => {
let data = { ...values };
// Ensure the date is in the correct format
const formattedDate = dayjs(data.date).format('YYYY-MM-DD');
console.log("Formatted Date:", formattedDate);
// Ensure the time is in the correct format
const formattedTime = data.time.length === 5 ? `${data.time}:00` : data.time;
console.log("Formatted Time:", formattedTime);
// Combine the formatted date and time
const dateTimeString = `${formattedDate} ${formattedTime}`;
console.log("Combined dateTime string:", dateTimeString);
// Parse the combined string with dayjs
const dateTime = dayjs(dateTimeString, 'YYYY-MM-DD HH:mm:ss');
console.log("Parsed dateTime:", dateTime);
// Check if the parsed dateTime is valid
if (!dateTime.isValid()) {
console.error("Invalid dateTime format");
return;
}
data.dateTime = dateTime.toISOString();
data.location = data.location.trim();
if (typeof data.postalCode === 'string') {
data.postalCode = data.postalCode.trim();
}
data.status = data.status.trim();
console.log("Data to be sent:", data);
instance.post("/schedule", data)
.then((res) => {
console.log(res.data);
navigate(-1);
})
};
return (
<section className="flex flex-col items-center justify-center gap-20 py-8 md:py-10">
<div className="w-full flex items-start">
<Button
variant="light"
onPress={() => navigate(-1)}
>
Back
</Button>
<div className="flex-grow text-center">
<p className="text-3xl font-bold">Add New Schedule</p>
</div>
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ isValid, dirty }) => (
<Form className="flex flex-col gap-4">
<div className="flex gap-8">
<NextUIFormikDatePicker
label="Date"
name="date"
className="max-w-[280px]"
/>
<NextUIFormikInput
type='time'
label="Time"
name="time"
placeholder=""
/>
</div>
<div className="flex gap-8">
<NextUIFormikInput
type="text"
label="Location"
name="location"
placeholder="Enter the location"
/>
<NextUIFormikInput
type="text"
label="Postal Code"
name="postalCode"
placeholder="123456"
/>
</div>
<NextUIFormikRadioGroup
label="Status"
name="status"
className="flex gap-2"
>
<Radio value="Up coming">Up coming</Radio>
<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>
</Form>
)}
</Formik>
</section >
)
}

View File

@@ -0,0 +1,164 @@
import { useState, useEffect } from "react";
import { Formik, Form } from "formik";
import * as yup from "yup";
import { Button, Radio } from "@nextui-org/react";
import { NextUIFormikRadioGroup } from "../components/NextUIFormikRadioButton";
import { NextUIFormikDatePicker } from "../components/NextUIFormikDatePicker";
import NextUIFormikInput from "../components/NextUIFormikInput";
import { useNavigate, useParams } from "react-router-dom";
import instance from "../security/http";
import dayjs from "dayjs";
// Validation schema
const validationSchema = yup.object().shape({
date: yup.date().required(),
time: yup.string().trim().required(),
location: yup.string().trim().min(15).max(50).required(),
postalCode: yup.string().matches(/^\d{6}$/, "Postal code must be exactly 6 digits").required(),
status: yup.string().trim().required()
});
export default function EditSchedulePage() {
const navigate = useNavigate();
const { id } = useParams();
const [schedule, setSchedule] = useState<any>({
date: "",
time: "",
location: "",
postalCode: "",
status: ""
});
useEffect(() => {
instance.get(`/schedule/${id}`).then((res) => {
const scheduleData = res.data;
scheduleData.date = dayjs(scheduleData.dateTime).format("YYYY-MM-DD");
scheduleData.time = dayjs(scheduleData.dateTime).format("HH:mm");
console.log("Fetched schedule data:", scheduleData);
setSchedule(scheduleData);
}).catch((err) => {
console.error("Error fetching schedule:", err);
});
}, [id]);
const handleSubmit = (values: any) => {
let data = { ...values };
// Ensure the date is in the correct format
const formattedDate = dayjs(data.date).format("YYYY-MM-DD");
console.log("Formatted Date:", formattedDate);
// Ensure the time is in the correct format
const formattedTime = data.time.length === 5 ? `${data.time}:00` : data.time;
console.log("Formatted Time:", formattedTime);
// Combine the formatted date and time
const dateTimeString = `${formattedDate} ${formattedTime}`;
console.log("Combined dateTime string:", dateTimeString);
// Parse the combined string with dayjs
const dateTime = dayjs(dateTimeString, "YYYY-MM-DD HH:mm:ss");
console.log("Parsed dateTime:", dateTime);
// Check if the parsed dateTime is valid
if (!dateTime.isValid()) {
console.error("Invalid dateTime format");
return;
}
data.dateTime = dateTime.toISOString();
data.location = data.location.trim();
if (typeof data.postalCode === "string") {
data.postalCode = data.postalCode.trim();
}
data.status = data.status.trim();
console.log("Data to be sent:", data);
instance.put(`/schedule/${id}`, data)
.then((res) => {
console.log(res.data);
navigate(-1);
})
.catch((error) => {
console.error("Error updating schedule:", error);
});
};
return (
<section className="flex flex-col items-center justify-center gap-20 py-8 md:py-10">
<div className="w-full flex items-start">
<Button
variant="light"
onPress={() => navigate(-1)}
>
Back
</Button>
<div className="flex-grow text-center">
<p className="text-3xl font-bold">Add New Schedule</p>
</div>
</div>
<Formik
enableReinitialize
initialValues={schedule}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ isValid, dirty }) => (
<Form className="flex flex-col gap-4">
<div className="flex gap-8">
<NextUIFormikDatePicker
label="Date"
name="date"
className="max-w-[284px]"
/>
<NextUIFormikInput
type='time'
label="Time"
name="time"
placeholder=""
/>
</div>
<div className="flex gap-8">
<NextUIFormikInput
type="text"
label="Location"
name="location"
placeholder="Enter the location"
/>
<NextUIFormikInput
type="text"
label="Postal Code"
name="postalCode"
placeholder="123456"
/>
</div>
<div>
<NextUIFormikRadioGroup
label="Status"
name="status"
className="flex gap-2"
>
<Radio value="Up coming">Up coming</Radio>
<Radio value="On going">On going</Radio>
<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>
</Form>
)}
</Formik>
</section>
)
}

View File

@@ -1,4 +1,4 @@
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
import instance from "../security/http";
import { PencilSquareIcon, PlusIcon, TrashDeleteIcon } from "../icons";
@@ -13,7 +13,7 @@ interface Schedule {
}
export default function ManageSchedulePage() {
const navigate = useNavigate();
const [scheduleList, setScheduleList] = useState<Schedule[]>([]);
const [scheduleIdToDelete, setScheduleIdToDelete] = useState<number | null>(null);
const { isOpen, onOpen, onOpenChange } = useDisclosure();
@@ -36,6 +36,11 @@ export default function ManageSchedulePage() {
});
}, []);
const handleEdit = (id: number) => {
navigate(`edit-schedule/${id}`);
};
const deleteSchedule = () => {
if (scheduleIdToDelete !== null) {
instance.delete(`/schedule/${scheduleIdToDelete}`)
@@ -96,7 +101,11 @@ export default function ManageSchedulePage() {
<div className="inline-block text-center justify-center flex flex-row gap-10">
<p className="text-3xl font-bold">Admin Karang Guni Schedule</p>
<Link to="/Addschedules">
<Button isIconOnly color="primary">
<Button
isIconOnly
color="primary"
onPress={() => navigate("create-schedule")}
>
<PlusIcon />
</Button>
</Link>
@@ -122,8 +131,21 @@ export default function ManageSchedulePage() {
<TableCell>{schedule.postalCode}</TableCell>
<TableCell>{schedule.status}</TableCell>
<TableCell>
<Link to={`/Editschedules/${schedule.id}`}><Button isIconOnly color="success" variant="light"><PencilSquareIcon /></Button></Link>
<Button isIconOnly color="danger" variant="light" onPress={() => { setScheduleIdToDelete(schedule.id); onOpen(); }}><TrashDeleteIcon /></Button>
<Button
isIconOnly
variant="light"
color="success"
onPress={() => handleEdit(schedule.id)}
>
<PencilSquareIcon />
</Button>
<Button
isIconOnly
color="danger"
variant="light"
onPress={() => { setScheduleIdToDelete(schedule.id); onOpen(); }}>
<TrashDeleteIcon />
</Button>
</TableCell>
</TableRow>
))}

View File

@@ -98,13 +98,15 @@ export default function SchedulePage() {
return (
<div className="w-full h-full">
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<h1>Karang Guni Schedule</h1>
<div className="flex flex-row gap-10">
<p className="text-2xl font-bold">Karang Guni Schedule</p>
<div className="flex gap-4">
{/* Search Input */}
<Input
value={search}
className="w-[400px]"
placeholder="Search Address/Postal"
placeholder="Search"
onChange={onSearchChange}
onKeyDown={onSearchKeyDown}
endContent={
@@ -119,6 +121,7 @@ export default function SchedulePage() {
}
/>
</div>
</div>
<div className="flex flex-col gap-8">
<Table aria-label="Schedule Table">
<TableHeader>
@@ -145,7 +148,7 @@ export default function SchedulePage() {
<TableCell>{schedule.location}</TableCell>
<TableCell>{schedule.postalCode}</TableCell>
<TableCell>
<Chip color={getStatusColor(schedule.status)}>
<Chip variant="flat" color={getStatusColor(schedule.status)}>
{schedule.status}
</Chip>
</TableCell>