From 099609ad7e480269bab24ff3c948e32ffc8f1197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ben=20B=C3=B6ckmann?= <75972296+bencodes07@users.noreply.github.com> Date: Thu, 5 Sep 2024 21:59:40 +0200 Subject: [PATCH] refactor: improvements made to appointment booking and scheduler display --- src/app/dashboard/bookings/page.tsx | 437 ++++++++++++++++++---------- src/types/index.d.ts | 19 ++ 2 files changed, 303 insertions(+), 153 deletions(-) diff --git a/src/app/dashboard/bookings/page.tsx b/src/app/dashboard/bookings/page.tsx index 03dde34..aab986e 100644 --- a/src/app/dashboard/bookings/page.tsx +++ b/src/app/dashboard/bookings/page.tsx @@ -15,7 +15,7 @@ import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { extractNameInitials } from "@/lib/utils"; import { useRouter } from "next/navigation"; import Scheduler from "@/components/dashboard/scheduler/Scheduler"; -import { FaPlus, FaRegCircleQuestion } from "react-icons/fa6"; +import { FaPlus, FaRegCircleQuestion, FaArrowLeft } from "react-icons/fa6"; import { AlertDialog, AlertDialogCancel, @@ -40,16 +40,16 @@ import { Progress } from "@/components/ui/progress"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Calendar } from "@/components/ui/calendar"; import { useDashboardData } from "@/components/dashboard/DashboardContext"; -import { type Appointment } from "@/types"; +import { type Appointment, type Company } from "@/types"; function Bookings() { - const user = useDashboardData().user; + const { user } = useDashboardData(); const [searchQuery, setSearchQuery] = useState(""); const [appointments, setAppointments] = useState([ { id: 1, - from: new Date(2024, 8, 3, 14, 30), - to: new Date(2024, 8, 3, 15, 30), + from: new Date(2024, 8, 3, 18, 30), + to: new Date(2024, 8, 3, 19, 30), title: "Scrum Meeting", description: "Weekly team sync", companyId: "1", @@ -70,8 +70,8 @@ function Bookings() { }, { id: 3, - from: new Date(2024, 8, 6, 17, 30), - to: new Date(2024, 8, 6, 18, 30), + from: new Date(2024, 8, 6, 21, 30), + to: new Date(2024, 8, 6, 22, 30), title: "Client Presentation", description: "Presenting project progress", companyId: "2", @@ -80,112 +80,63 @@ function Bookings() { status: "BOOKED" } ]); + const [companies, setCompanies] = useState([ + { + id: "1", + name: "Github", + createdAt: "2024-01-01", + description: "Version control platform", + owner: user!, + members: [], + settings: { + appointmentDuration: 60, + appointmentBuffer: 15, + appointmentTypes: ["Code Review", "Project Planning"], + appointmentLocations: ["Online", "Office"], + openingHours: { + from: "09:00", + to: "17:00" + } + } + } + // Add more companies as needed + ]); useEffect(() => { - if (false) setAppointments([]); // This is a dummy condition to avoid unused variable warning (events) + if (false) setAppointments([]); + setCompanies([]); }, []); const [filteredAppointments, setFilteredAppointments] = useState(appointments); - const router = useRouter(); - const [select, setSelect] = useState(""); - const [date, setDate] = useState(new Date()); - - const [bookingMode, setBookingMode] = useState(0); - const [bookingContent, setBookingContent] = useState({ - title: "Select your company", - description: "With which company do you want to book this appointment?", - select: { - active: true, - content: ["Github", "Böckmann GmbH", "MeetMate"] - }, - calendar: { - active: false - }, - progress: 33 - }); - - const [bookingResult, setBookingResult] = useState<{ company: string; date: Date | undefined; time: string }>({ - company: "", - date: new Date(), - time: "" + const [bookingState, setBookingState] = useState({ + step: 0, + selectedCompany: "", + selectedDate: new Date(), + selectedTime: "", + isLoading: false, + error: null as string | null }); useEffect(() => { - if (searchQuery === "") { - setFilteredAppointments(appointments); - return; - } else { - setFilteredAppointments( - appointments.filter((appointment) => appointment.title.toLowerCase().includes(searchQuery.toLowerCase())) - ); - } + const filtered = + searchQuery === "" + ? appointments + : appointments.filter((appointment) => appointment.title.toLowerCase().includes(searchQuery.toLowerCase())); + setFilteredAppointments(filtered); }, [searchQuery, appointments]); - function updateBookingProgress() { - if (bookingMode === 1 && select !== "") { - setBookingResult({ ...bookingResult, company: select }); - setSelect(""); - setBookingContent({ - title: "Select your Date", - description: "When would you like to book this appointment?", - select: { - active: false, - content: ["13:00 - 14:00", "15:00 - 16:00", "19:00 - 20:30"] - }, - calendar: { - active: true - }, - progress: 66 - }); - setBookingMode(bookingMode + 1); - } else if (bookingMode === 2) { - setBookingResult({ ...bookingResult, date }); - setBookingContent({ - title: "Select your Slot", - description: "At what time would you like to book this appointment?", - select: { - active: true, - content: ["13:00 - 14:00", "15:00 - 16:00", "19:00 - 20:30"] - }, - calendar: { - active: false - }, - progress: 80 - }); - setBookingMode(bookingMode + 1); - } else if (bookingMode === 3 && select !== "") { - setBookingResult({ ...bookingResult, time: select }); - setBookingContent({ - title: "Success", - description: "Your appointment was successfully booked", - select: { - active: false, - content: ["13:00 - 14:00", "15:00 - 16:00", "19:00 - 20:30"] - }, - calendar: { - active: false - }, - progress: 100 - }); - console.log(bookingResult); - } else if (bookingMode === 0) { - setBookingContent({ - title: "Select your company", - description: "With which company do you want to book this appointment?", - select: { - active: true, - content: ["Github", "Böckmann GmbH", "MeetMate"] - }, - calendar: { - active: false - }, - progress: 33 - }); - setBookingMode(1); - } - } + const handleAppointmentCancel = (appointment: Appointment): "success" | "error" => { + console.log("Appointment cancelled", appointment); + // Implement cancellation logic here + return "success"; + }; + + const handleAppointmentChange = (appointment: Appointment) => { + console.log("Appointment change requested", appointment); + // Implement change logic here + }; const logout = async () => { try { @@ -197,20 +148,198 @@ function Bookings() { } }; - const handleAppointmentCancel = (appointment: Appointment): "success" | "error" => { - console.log("Appointment cancelled", appointment); - // Here you would typically make an API call to cancel the appointment - // For now, we'll just update the local state - // const updatedAppointments = appointments.map((app) => - // app.id === cleanedAppointment.id ? { ...app, status: "CANCELLED" } : app - // ); - // setAppointments(updatedAppointments); - return "success"; + const updateBookingStep = (step: number) => { + setBookingState((prev) => ({ + ...prev, + step, + error: null // Clear any previous errors when moving to a new step + })); }; - const handleAppointmentChange = (appointment: Appointment) => { - // Implement appointment change logic here - console.log("Appointment change requested", appointment); + const handleSelectChange = (value: string, field: "selectedCompany" | "selectedTime") => { + setBookingState((prev) => ({ + ...prev, + [field]: value + })); + }; + + const handleDateChange = (date: Date | undefined) => { + // eslint-disable-next-line eqeqeq + if (date != null) { + setBookingState((prev) => ({ + ...prev, + selectedDate: date + })); + } + }; + + const calculateSchedulerHours = (calcAppointments: Appointment[]): { open: string; close: string } => { + const DEFAULT_INTERVAL = 6; // 7 hours + const DEFAULT_START = 9; // Default start at 9 AM + const DEFAULT_END = 15; // Default end at 3 PM (6 hours later) + + if (calcAppointments.length === 0) { + return { + open: `${DEFAULT_START.toString().padStart(2, "0")}:00`, + close: `${DEFAULT_END.toString().padStart(2, "0")}:00` + }; + } + + // Find the earliest start time and latest end time + const earliestTime = Math.min(...calcAppointments.map((a) => a.from.getHours())); + const latestTime = Math.max(...calcAppointments.map((a) => a.to.getHours())); + + let startHour: number; + let endHour: number; + + if (latestTime - earliestTime >= DEFAULT_INTERVAL) { + // Rare case: appointments span more than 6 hours + startHour = earliestTime; + endHour = latestTime + 1; // Add 1 hour for padding + } else { + // Normal case: shift the 6-hour window to include all appointments + endHour = Math.min(24, Math.max(latestTime + 1, earliestTime + DEFAULT_INTERVAL)); + startHour = Math.max(0, endHour - DEFAULT_INTERVAL); + } + + // Ensure we always have a 6-hour window minimum + if (endHour - startHour < DEFAULT_INTERVAL) { + endHour = Math.min(24, startHour + DEFAULT_INTERVAL); + } + + return { + open: `${startHour.toString().padStart(2, "0")}:00`, + close: `${endHour.toString().padStart(2, "0")}:00` + }; + }; + + // Calculate scheduler hours whenever appointments change + const schedulerHours = calculateSchedulerHours(appointments); + + const handleBookAppointment = async () => { + setBookingState((prev) => ({ ...prev, isLoading: true, error: null })); + + // Log the appointment data + console.log("Booking appointment with the following details:", { + company: bookingState.selectedCompany, + date: bookingState.selectedDate, + time: bookingState.selectedTime + }); + + try { + // Simulated API call + // const response = await fetch('/api/book-appointment', { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: JSON.stringify({ + // companyId: bookingState.selectedCompany, + // date: bookingState.selectedDate, + // time: bookingState.selectedTime, + // }), + // }); + + // if (!response.ok) { + // throw new Error('Failed to book appointment'); + // } + + // Simulate API delay + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // Simulate success or failure randomly + if (Math.random() > 0.2) { + // 80% success rate + updateBookingStep(4); // Success step + } else { + throw new Error("Failed to book appointment"); + } + } catch (error) { + setBookingState((prev) => ({ + ...prev, + error: error instanceof Error ? error.message : "An unknown error occurred" + })); + } finally { + setBookingState((prev) => ({ ...prev, isLoading: false })); + } + }; + + const renderBookingStep = () => { + switch (bookingState.step) { + case 0: + return ( + + Select your company + With which company do you want to book this appointment? + + + ); + case 1: + return ( + + Select your Date + When would you like to book this appointment? + + + ); + case 2: + return ( + + Select your Slot + At what time would you like to book this appointment? + + + ); + case 3: + return ( + + Confirm Booking + Please confirm your appointment details: +
+

Company: {companies.find((c) => c.id === bookingState.selectedCompany)?.name}

+

Date: {bookingState.selectedDate.toDateString()}

+

Time: {bookingState.selectedTime}

+
+
+ ); + case 4: + return ( + + Success + Your appointment was successfully booked + + ); + default: + return null; + } }; return ( @@ -224,7 +353,8 @@ function Bookings() { value={searchQuery} onChange={(e) => { setSearchQuery(e.target.value); - }}> + }} + /> - - ) : ( - +
+ {bookingState.step > 0 && bookingState.step < 4 && ( + + )} +
+ {bookingState.step === 4 ? ( - - - )} + )} +
- + @@ -345,7 +476,7 @@ function Bookings() {