feat(InterviewBooking): added error handing, button css and defined select on date

This commit is contained in:
darkicewolf50 2025-04-12 12:03:14 -06:00
parent 6aade88454
commit aa8e5c2577
3 changed files with 412 additions and 320 deletions

View File

@ -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;
}

View File

@ -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 <ahmadmuhammadofficial@gmail.com>
* @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 <ahmadmuhammadofficial@gmail.com>
* @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 (
<>
<form onSubmit={formsubmit}>
<div id="InterviewForm">
<div>
<label for="name">Name:</label>
<input
type="text"
id="fname"
name="intervieweeName"
placeholder="Jaeinceins"
required
/>
<p>(what to call you)</p>
</div>
<div>
<label for="email">UCalgary Email:</label>
<input
type="text"
id="email"
name="intervieweeEmail"
placeholder="jaeinceins.bhaja@ucalgary.ca"
required
/>
<p>(for interview confirmation email)</p>
</div>
</div>
/**
* @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 <darkicewolf50@gmail.com>
*/
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 (
<>
<form onSubmit={formsubmit}>
<div id="InterviewForm">
<div>
<label for="name">Name:</label>
<input
type="text"
id="fname"
name="intervieweeName"
placeholder="Jaeinceins"
required
/>
<p>(what to call you)</p>
</div>
<div>
<label for="email">UCalgary Email:</label>
<input
type="text"
id="email"
name="intervieweeEmail"
placeholder="jaeinceins.bhaja@ucalgary.ca"
required
/>
<p>(for interview confirmation email)</p>
</div>
</div>
{/* Time Slot Selector */}
<TimeDateSelector
onTimeSlotSelect={(timeSlot) => setSelectedTimeSlot(timeSlot)}
timeDateSelectorGet={getTimeDates}
/>
<div id="InterviewText">
<h4>
What to do if I cannot make it to any of the avaliable time slots or
need to rescedule?
</h4>
<p>
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.
</p>
<p>
Please email us at{" "}
<a href="mailto:uofcbaja@gmail.com">uofcbaja@gmail.com</a> to work
out an alternate interview time or for rescheduling.
</p>
</div>
<p id="InterviewError"> </p>
<div id="InterviewSubmit">
<button type="submit" disabled={isButtonDisabled}>
Submit
</button>
</div>
</form>
{/* Time Slot Selector */}
<TimeDateSelector
onTimeSlotSelect={(timeSlot) => setSelectedTimeSlot(timeSlot)}
timeDateSelectorGet={getTimeDates}
/>
<div id="InterviewText">
<h4>
What to do if I cannot make it to any of the avaliable time slots or
need to rescedule?
</h4>
<p>
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.
</p>
<p>
Please email us at{" "}
<a href="mailto:uofcbaja@gmail.com">uofcbaja@gmail.com</a> to work
out an alternate interview time or for rescheduling.
</p>
</div>
{/* Success Dialog */}
<dialog ref={dialogRef}>
{" "}
{/* Add the `ref` attribute */}
<h2>Booking Successful!</h2>
<p>
Thank you for booking your interview slot. Well contact you soon.
</p>
<h4>
What to do if I cannot make it to any of the avaliable time slots or
need to rescedule?
</h4>
<p>
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.
</p>
<p>
Please email us at{" "}
<a href="mailto:uofcbaja@gmail.com">uofcbaja@gmail.com</a> to work out
an alternate interview time or for rescheduling.
</p>
</dialog>
</>
);
<p id="InterviewError"> </p>
<div id="InterviewSubmit">
<button
type="submit"
disabled={isButtonDisabled}>
Submit
</button>
</div>
</form>
{/* Success Dialog */}
<dialog ref={dialogRef}>
{" "}
{/* Add the `ref` attribute */}
<h2>Booking Successful!</h2>
<p>
Thank you for booking your interview slot. Well contact you soon.
</p>
<h4>
What to do if I cannot make it to any of the avaliable time slots or
need to rescedule?
</h4>
<p>
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.
</p>
<p>
Please email us at{" "}
<a href="mailto:uofcbaja@gmail.com">uofcbaja@gmail.com</a> to work out
an alternate interview time or for rescheduling.
</p>
</dialog>
</>
);
};
export default InterviewForm;

View File

@ -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 <ahmadmuhammadofficial@gmail.com>
*/
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 <ahmadmuhammadofficial@gmail.com>, Brock <darkicewolf50@gmail.com>
* @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 <ahmadmuhammadofficial@gmail.com>, Brock <darkicewolf50@gmail.com>
* @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 <darkicewolf50@gmail.com>
*/
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 <darkicewolf50@gmail.com>
*/
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 (
<div id="TimeSlotSelector">
{Object.keys(allDatesAvailable).length > 0 ? (
<>
<div className="TimeSlot">
<h4>Interview Date</h4>
<label htmlFor="date-picker">
<p>Select a Date:</p>
</label>
<DatePicker
selected={selectedDate}
onChange={handleDateChange}
inline
filterDate={isDateAvailable} // Filter/grey out unavailable dates
dateFormat="yyyy-MM-dd"
required // Make date selection required
/>
</div>
<div className="TimeSlot">
<h4>Interview Time</h4>
{!selectedDate ? (
<>
<p>Available Time Slots:</p>
<div
style={{
height: "241.633px",
display: "flex",
alignItems: "center",
}}
>
<p>Please select the a date to see time slots.</p>
</div>
</>
) : (
<>
<label htmlFor="time-picker">
<p>
Available Time Slots for{" "}
{selectedDate.toISOString().split("T")[0]}:
</p>
</label>
{selectedDate === undefined ? (
<>
<p>Please select a date.</p>
</>
) : timeSlotsAvialable !== "" ? (
<>
{Object.values(timeSlotsAvialable).map((time) => {
return (
<button
key={time}
type="button"
onClick={(self) => {
handleTimeSlotChange(self);
}}
>
{time}
</button>
);
})}
</>
) : (
<>
<p>No available time slots for the selected date.</p>
</>
)}
</>
)}
</div>
{/* {selectedDate && selectedTime && (
let startTime = e.currentTarget.dataset.time;
setSelectedTime(startTime);
onTimeSlotSelect({
date: selectedDate.toLocaleDateString(),
startTime: selectedTime,
});
};
return (
<div id="TimeSlotSelector">
{Object.keys(allDatesAvailable).length > 0 ? (
<>
<div className="TimeSlot">
<h4>Interview Date</h4>
<label htmlFor="date-picker">
<p>Select a Date:</p>
</label>
<DatePicker
selected={selectedDate}
onChange={handleDateChange}
inline
filterDate={isDateAvailable} // Filter/grey out unavailable dates
dateFormat="yyyy-MM-dd"
required // Make date selection required
/>
</div>
<div className="TimeSlot">
<h4>Interview Time</h4>
{!selectedDate ? (
<>
<p>Available Time Slots:</p>
<div
style={{
height: "241.633px",
display: "flex",
alignItems: "center",
}}>
<p>Please select the a date to see time slots.</p>
</div>
</>
) : (
<>
<label htmlFor="time-picker">
<p>
Available Time Slots for{" "}
{selectedDate.toISOString().split("T")[0]}:
</p>
</label>
{selectedDate === undefined ? (
<>
<p>Please select a date.</p>
</>
) : timeSlotsAvialable !== "" ? (
<>
{Object.values(timeSlotsAvialable).map((time) => {
// console.log(timeSlotsAvialable.indexOf(time));
return (
<button
className={
"TimeSlotSide" +
(timeSlotsAvialable.indexOf(time) % 2)
}
key={time}
type="button"
data-time={time}
onClick={handleTimeSlotChange}>
{time.slice(0, 5)}
</button>
);
})}
</>
) : (
<>
<p>No available time slots for the selected date.</p>
</>
)}
</>
)}
</div>
{/* {selectedDate && selectedTime && (
<div>
<p>
You have selected:
@ -150,10 +172,10 @@ export default function TimeDateSelector({
Time: {selectedTime}
</p>
</div>)} */}
</>
) : (
<p>Loading ...</p>
)}
</div>
);
</>
) : (
<p id="interviewLoading">Loading ...</p>
)}
</div>
);
}