From aa8e5c25770a3a309be0c71c3cc5a82decf641ab Mon Sep 17 00:00:00 2001 From: darkicewolf50 Date: Sat, 12 Apr 2025 12:03:14 -0600 Subject: [PATCH] feat(InterviewBooking): added error handing, button css and defined select on date --- src/Interivew Booking/InterviewBooking.css | 142 ++++++---- src/Interivew Booking/InterviewForm.jsx | 300 ++++++++++++--------- src/Interivew Booking/TimeDateSelector.jsx | 290 +++++++++++--------- 3 files changed, 412 insertions(+), 320 deletions(-) diff --git a/src/Interivew Booking/InterviewBooking.css b/src/Interivew Booking/InterviewBooking.css index afa6d89..a9241af 100644 --- a/src/Interivew Booking/InterviewBooking.css +++ b/src/Interivew Booking/InterviewBooking.css @@ -1,84 +1,122 @@ :root { - --interviewspacing: clamp(5px, 2.5svw, 200px); - --interviewwidth: 260px; + --interviewspacing: clamp(5px, 2.5svw, 200px); + --interviewwidth: 260px; } #InterviewBooking header { - display: flex; - flex-direction: row; - background-color: lightslategray; - justify-content: space-around; - margin: 0px; + display: flex; + flex-direction: row; + background-color: lightslategray; + justify-content: space-around; + margin: 0px; } #InterviewForm { - display: flex; - flex-direction: row; - width: 100%; - justify-content: center; + display: flex; + flex-direction: row; + width: 100%; + justify-content: center; } #InterviewForm div { - display: flex; - flex-direction: column; - padding-left: var(--interviewspacing); - padding-right: var(--interviewspacing); - text-align: start; - width: var(--interviewwidth); + display: flex; + flex-direction: column; + padding-left: var(--interviewspacing); + padding-right: var(--interviewspacing); + text-align: start; + width: var(--interviewwidth); } #InterviewForm div label { - font-weight: 700; + font-weight: 700; } #InterviewForm div p { - font-size: x-small; - margin: 0px; + font-size: x-small; + margin: 0px; } #InterviewForm div input { - margin-top: 1svh; - height: 30px; + margin-top: 1svh; + height: 30px; } #MainForm { - align-items: center; - display: flex; - flex-direction: column; + align-items: center; + display: flex; + flex-direction: column; } #MainForm form { - display: flex; - flex-direction: column; - align-items: center; - width: 80%; + display: flex; + flex-direction: column; + align-items: center; + width: 80%; } #TimeSlotSelector { - display: flex; - flex-direction: row; - width: 100%; - justify-content: center; + display: flex; + flex-direction: row; + width: 100%; + justify-content: center; } .TimeSlot { - padding-left: var(--interviewspacing); - padding-right: var(--interviewspacing); - width: var(--interviewwidth); + padding-left: var(--interviewspacing); + padding-right: var(--interviewspacing); + width: var(--interviewwidth); } .TimeSlot h4 { - margin: 0px; - margin-top: 30px; + margin: 0px; + margin-top: 30px; } .TimeSlot p { - margin: 0px; - margin-bottom: 20px; + margin: 0px; + margin-bottom: 20px; } #InterviewSubmit { - display: flex; - justify-content: center; - width: inherit; + display: flex; + justify-content: center; + width: inherit; } #InterviewSubmit button { - background-color: lightgreen; - width: 20%; - height: 5svh; - margin-bottom: 5svh; - font-size: large; - border: none; + background-color: lightgreen; + width: 20%; + height: 5svh; + margin-bottom: 5svh; + font-size: large; + border: none; } #InterviewText { - padding-left: var(--interviewspacing); - padding-right: var(--interviewspacing); - width: 80%; + padding-left: var(--interviewspacing); + padding-right: var(--interviewspacing); + width: 80%; +} + +:root { + /* used for editing time buttons */ + --TimeSlotSideWidth: 48%; + --TimeSlotSidePaddingTopBottom: 1svh 0px; + --TimeSlotSideMarginTopBottom: 0.25svh 1%; +} + +.TimeSlotSide0 { + cursor: pointer; + border: none; + + width: var(--TimeSlotSideWidth); + + padding: var(--TimeSlotSidePaddingTopBottom); + margin: var(--TimeSlotSideMarginTopBottom); +} + +.TimeSlotSide1 { + cursor: pointer; + border: none; + width: var(--TimeSlotSideWidth); + + padding: var(--TimeSlotSidePaddingTopBottom); + margin: var(--TimeSlotSideMarginTopBottom); +} + +#interviewLoading { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + height: 334.3px; +} + +#CurrentSelected { + background-color: lightseagreen; } diff --git a/src/Interivew Booking/InterviewForm.jsx b/src/Interivew Booking/InterviewForm.jsx index d658afe..2d30eab 100644 --- a/src/Interivew Booking/InterviewForm.jsx +++ b/src/Interivew Booking/InterviewForm.jsx @@ -1,5 +1,5 @@ import { useState, useRef } from "react"; -import TimeDateSelector from "./TimeDateSelector"; // Import the TimeSlotSelector component +import TimeDateSelector from "./TimeDateSelector"; /** * @param {null} null - Takes in nothing @@ -10,149 +10,181 @@ import TimeDateSelector from "./TimeDateSelector"; // Import the TimeSlotSelecto */ const InterviewForm = () => { - const [isButtonDisabled, setIsButtonDisabled] = useState(false); - const dialogRef = useRef(null); - const [selectedTimeSlot, setSelectedTimeSlot] = useState(null); - const [getTimeDates, setGetTimeDates] = useState(""); + const [isButtonDisabled, setIsButtonDisabled] = useState(false); + const dialogRef = useRef(null); + const [selectedTimeSlot, setSelectedTimeSlot] = useState(null); + const [getTimeDates, setGetTimeDates] = useState(""); - /** - * @param {String HTML} event - Takes in form info - * @returns {null} null - Returns in nothing - * @description - * @author Ahmad - * @todo CSS - */ - const formsubmit = async (event) => { - event.preventDefault(); + /** + * @param {String HTML} event - Takes in form info + * @returns {null} null - Returns in nothing + * @description submits the form with the appropriate information + * @author Ahmad + * @todo imporper email and other erros from backend + */ + const formsubmit = async (event) => { + const errorLine = document.getElementById("InterviewError"); - if (!selectedTimeSlot) { - alert("Please select a time slot!"); - return; - } + event.preventDefault(); - // disable button to stop multiple requests - setIsButtonDisabled(true); + if (selectedTimeSlot) { + console.log(selectedTimeSlot); + if (selectedTimeSlot.date !== null && selectedTimeSlot.startTime !== "") { + errorLine.innerHTML = " "; + // disable button to stop multiple requests + setIsButtonDisabled(true); - // await new Promise((res) => setTimeout(res, 1000)); - const formData = new FormData(event.target); - const formObject = Object.fromEntries(formData.entries()); - formObject.date = selectedTimeSlot["date"]; // Add the selected time slot to form data - formObject.startTime = selectedTimeSlot["startTime"]; - console.log("Form Data:", formObject); - const res = await fetch( - "https://bajabackend.bajacloud.duckdns.org/SelectInterview", - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(formObject), - } - ); - // const res = await fetch( - // "http://127.0.0.1:8000/SelectInterview", - // { - // method: "POST", - // headers: { - // "Content-Type": "application/json", - // }, - // body: JSON.stringify(formObject), - // } - // ); + // await new Promise((res) => setTimeout(res, 1000)); + const formData = new FormData(event.target); + const formObject = Object.fromEntries(formData.entries()); + formObject.date = selectedTimeSlot["date"]; // Add the selected time slot to form data + formObject.startTime = selectedTimeSlot["startTime"]; + console.log("Form Data:", formObject); - let data = await res.json(); + const res = await fetch( + "https://bajabackend.bajacloud.duckdns.org/SelectInterview", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formObject), + } + ); + // const res = await fetch( + // "http://127.0.0.1:8000/SelectInterview", + // { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify(formObject), + // } + // ); - if (data["body"]["Success"] === true) { - dialogRef.current.showModal(); - } else { - setGetTimeDates(getTimeDates + "i"); - } + let data = await res.json(); - setIsButtonDisabled(false); - }; + if (data["body"]["Success"] === true) { + dialogRef.current.showModal(); + } else { + setGetTimeDates(getTimeDates + "i"); + } + x; + } else { + formSubmitTimeErorrs(selectedTimeSlot, errorLine); + } + } else { + formSubmitTimeErorrs(selectedTimeSlot, errorLine); + } + setIsButtonDisabled(false); + }; - return ( - <> -
-
-
- - -

(what to call you)

-
-
- - -

(for interview confirmation email)

-
-
+ /** + * @param {Object} missingError - Takes in object to find what is missing from it + * @param {HTMLSelectElement} - display line on page + * @returns {null} null - Returns in nothing + * @description A separated function to handle all timeslot errors possible states and displays the appropriate message on the error line + * @author Brock + */ + const formSubmitTimeErorrs = (missingError, errorLine) => { + if (!missingError) { + errorLine.innerHTML = "Please Select a Date and a Time"; + } + // impossible state + /* + else if (missingError.date === null) { + errorLine.innerHTML = "Please Select a Date"; + */ + else if (missingError.startTime) { + errorLine.innerHTML = "Please Select a Time"; + } + }; + return ( + <> + +
+
+ + +

(what to call you)

+
+
+ + +

(for interview confirmation email)

+
+
- {/* Time Slot Selector */} - setSelectedTimeSlot(timeSlot)} - timeDateSelectorGet={getTimeDates} - /> -
-

- What to do if I cannot make it to any of the avaliable time slots or - need to rescedule? -

-

- While we highly encourage sceduling an interview in one of the above - time slots, we recongize that not everyone can make it work with - their personal and university schedules. -

-

- Please email us at{" "} - uofcbaja@gmail.com to work - out an alternate interview time or for rescheduling. -

-
-

-
- -
- + {/* Time Slot Selector */} + setSelectedTimeSlot(timeSlot)} + timeDateSelectorGet={getTimeDates} + /> +
+

+ What to do if I cannot make it to any of the avaliable time slots or + need to rescedule? +

+

+ While we highly encourage sceduling an interview in one of the above + time slots, we recongize that not everyone can make it work with + their personal and university schedules. +

+

+ Please email us at{" "} + uofcbaja@gmail.com to work + out an alternate interview time or for rescheduling. +

+
- {/* Success Dialog */} - - {" "} - {/* Add the `ref` attribute */} -

Booking Successful!

-

- Thank you for booking your interview slot. We’ll contact you soon. -

-

- What to do if I cannot make it to any of the avaliable time slots or - need to rescedule? -

-

- While we highly encourage sceduling an interview in one of the above - time slots, we recongize that not everyone can make it work with their - personal and university schedules. -

-

- Please email us at{" "} - uofcbaja@gmail.com to work out - an alternate interview time or for rescheduling. -

-
- - ); +

+ +
+ +
+ + + {/* Success Dialog */} + + {" "} + {/* Add the `ref` attribute */} +

Booking Successful!

+

+ Thank you for booking your interview slot. We’ll contact you soon. +

+

+ What to do if I cannot make it to any of the avaliable time slots or + need to rescedule? +

+

+ While we highly encourage sceduling an interview in one of the above + time slots, we recongize that not everyone can make it work with their + personal and university schedules. +

+

+ Please email us at{" "} + uofcbaja@gmail.com to work out + an alternate interview time or for rescheduling. +

+
+ + ); }; export default InterviewForm; diff --git a/src/Interivew Booking/TimeDateSelector.jsx b/src/Interivew Booking/TimeDateSelector.jsx index 364b789..269a4bf 100644 --- a/src/Interivew Booking/TimeDateSelector.jsx +++ b/src/Interivew Booking/TimeDateSelector.jsx @@ -2,145 +2,167 @@ import React, { useState, useEffect } from "react"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; +/** + * @param {Function} onTimeSlotSelect - Used to pass back up the selected values from child component + * @param {Function} timeDateSelectorGet - Used to display dates avaialable + * @returns {JSX.element} JSX - HTML and JS functionality + * @description Used for picking an interview date + * @author Ahmad + */ export default function TimeDateSelector({ - onTimeSlotSelect, - timeDateSelectorGet, + onTimeSlotSelect, + timeDateSelectorGet, }) { - const [allDatesAvailable, setAllDatesAvailable] = useState({}); - const [selectedDate, setSelectedDate] = useState(null); - const [timeSlotsAvialable, setTimeSlotsAvialable] = useState([]); - const [selectedTime, setSelectedTime] = useState(""); + const [allDatesAvailable, setAllDatesAvailable] = useState({}); + const [selectedDate, setSelectedDate] = useState(null); + const [timeSlotsAvialable, setTimeSlotsAvialable] = useState([]); + const [selectedTime, setSelectedTime] = useState(""); + const [selectedTimeButton, setSelectedTimeButton] = useState(null); - useEffect(() => { - getInterviewDates(); - }, [timeDateSelectorGet]); + useEffect(() => { + getInterviewDates(); + }, [timeDateSelectorGet]); - /** - * @param {null} null - Takes in nothing - * @returns {null} null - Returns in nothing - * @description Gets interview timeslots and dates from backend - * @author Ahmad , Brock - * @todo refactor to not call backend so much, see useEffect above - */ - const getInterviewDates = async () => { - const res = await fetch( - "https://bajabackend.bajacloud.duckdns.org/getAppointments", - { method: "GET" } - ); - // const res = await fetch( - // "http://127.0.0.1:8000/getAppointments", - // { method: "GET" } - // ); - let json = await res.json(); - // console.log(json); - // can input dates right away, no other requirements to show it - let dates = await json["body"]["interviewDates"]; - setAllDatesAvailable(await dates); - }; + /** + * @param {null} null - Takes in nothing + * @returns {null} null - Returns in nothing + * @description Gets interview timeslots and dates from backend + * @author Ahmad , Brock + * @todo refactor to not call backend so much, see useEffect above + */ + const getInterviewDates = async () => { + const res = await fetch( + "https://bajabackend.bajacloud.duckdns.org/getAppointments", + { method: "GET" } + ); + // const res = await fetch( + // "http://127.0.0.1:8000/getAppointments", + // { method: "GET" } + // ); + let json = await res.json(); + // console.log(json); + // can input dates right away, no other requirements to show it + let dates = await json["body"]["interviewDates"]; + setAllDatesAvailable(await dates); + }; - // helper section + // helper section - /** - * @param {Date} date - Takes in a date object from the date picker - * @returns {null} null - Returns in nothing - * @description checks if date is available from the backend - * @author Brock - */ - const isDateAvailable = (date) => { - return Object.keys(allDatesAvailable).includes( - date.toISOString().split("T")[0] - ); - }; + /** + * @param {Date} date - Takes in a date object from the date picker + * @returns {null} null - Returns in nothing + * @description checks if date is available from the backend + * @author Brock + */ + const isDateAvailable = (date) => { + return Object.keys(allDatesAvailable).includes( + date.toISOString().split("T")[0] + ); + }; - const handleDateChange = (date) => { - setSelectedDate(date); // Capture the selected date in date object - const selectedDateStr = date.toISOString().split("T")[0]; + const handleDateChange = (date) => { + setSelectedDate(date); // Capture the selected date in date object + const selectedDateStr = date.toISOString().split("T")[0]; - // get and set time slots for a given day - setTimeSlotsAvialable(Object.keys(allDatesAvailable[selectedDateStr])); - setSelectedTime(""); // clear because of date change - }; + // get and set time slots for a given day + setTimeSlotsAvialable(Object.keys(allDatesAvailable[selectedDateStr])); + setSelectedTime(""); // clear because of date change + // set prematurely for better error messages + onTimeSlotSelect({ + date: selectedDateStr, + startTime: selectedTime, + }); + }; - const handleTimeSlotChange = (e) => { - let startTime = e.target.innerHTML; - setSelectedTime(startTime); + const handleTimeSlotChange = (e) => { + e.currentTarget.id = "CurrentSelected"; - onTimeSlotSelect({ - date: selectedDate.toLocaleDateString(), - startTime: selectedTime, - }); - }; + if (selectedTimeButton !== null) { + selectedTimeButton.id = ""; + } + setSelectedTimeButton(e.currentTarget); - return ( -
- {Object.keys(allDatesAvailable).length > 0 ? ( - <> -
-

Interview Date

- - -
-
-

Interview Time

- {!selectedDate ? ( - <> -

Available Time Slots:

-
-

Please select the a date to see time slots.

-
- - ) : ( - <> - - {selectedDate === undefined ? ( - <> -

Please select a date.

- - ) : timeSlotsAvialable !== "" ? ( - <> - {Object.values(timeSlotsAvialable).map((time) => { - return ( - - ); - })} - - ) : ( - <> -

No available time slots for the selected date.

- - )} - - )} -
- {/* {selectedDate && selectedTime && ( + let startTime = e.currentTarget.dataset.time; + setSelectedTime(startTime); + + onTimeSlotSelect({ + date: selectedDate.toLocaleDateString(), + startTime: selectedTime, + }); + }; + + return ( +
+ {Object.keys(allDatesAvailable).length > 0 ? ( + <> +
+

Interview Date

+ + +
+
+

Interview Time

+ {!selectedDate ? ( + <> +

Available Time Slots:

+
+

Please select the a date to see time slots.

+
+ + ) : ( + <> + + {selectedDate === undefined ? ( + <> +

Please select a date.

+ + ) : timeSlotsAvialable !== "" ? ( + <> + {Object.values(timeSlotsAvialable).map((time) => { + // console.log(timeSlotsAvialable.indexOf(time)); + return ( + + ); + })} + + ) : ( + <> +

No available time slots for the selected date.

+ + )} + + )} +
+ {/* {selectedDate && selectedTime && (

You have selected: @@ -150,10 +172,10 @@ export default function TimeDateSelector({ Time: {selectedTime}

)} */} - - ) : ( -

Loading ...

- )} -
- ); + + ) : ( +

Loading ...

+ )} +
+ ); }