Created
January 11, 2023 22:31
-
-
Save abel-masila/f7666d782e3ee85d6004157247bc2f3f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-unused-expressions */ | |
import React, { useState, useEffect } from "react"; | |
import { | |
AppBar, | |
Button, | |
Card, | |
Dialog, | |
DialogActions, | |
DialogContent, | |
FormControl, | |
FormControlLabel, | |
FormLabel, | |
IconButton, | |
InputLabel, | |
MenuItem, | |
Radio, | |
RadioGroup, | |
Select, | |
Slide, | |
Snackbar, | |
Step, | |
StepContent, | |
StepLabel, | |
Stepper, | |
TextField, | |
Toolbar, | |
Typography, | |
} from "@mui/material"; | |
// import { DatePicker } from '@mui/x-date-pickers-pro/DatePicker'; | |
// // or | |
// import { DatePicker } from '@mui/x-date-pickers/DatePicker'; | |
// // or | |
// import { DatePicker } from '@mui/x-date-pickers-pro'; | |
// // or | |
import { DatePicker } from "@mui/x-date-pickers"; | |
import { ListSubheader } from "@mui/material"; | |
// import {RadioGroup} from '@mui/material' | |
// import { RaisedButton } from "@mui/material"; | |
// import { FlatButton } from "@mui/material"; | |
import moment from "moment"; | |
// import { moment } from "@mui/material/moment"; | |
// import { DatePicker, DateTimePicker } from "@mui/x-date-pickers"; | |
// import { Dialog } from "@mui/material"; | |
// import { SelectField } from "@mui/material"; | |
// import { MenuItem } from "@mui/material"; | |
// import { TextField } from "@mui/material"; | |
// import { SnackBar } from "@mui/material"; | |
// import { Card } from "@mui/material"; | |
// import { Button } from "@mui/material"; | |
// import { | |
// Stepper, | |
// StepperClassKey, | |
// StepLabel, | |
// StepContent, | |
// } from "@mui/material/Stepper"; | |
// import { RadioGroup } from "@mui/material"; | |
import axios from "axios"; | |
import { Box } from "@mui/system"; | |
// import { DatePickerToolbar } from "@mui/x-date-pickers/DatePicker/DatePickerToolbar"; | |
// import { response } from "../../../api"; | |
const AppointmentApp = () => { | |
const API_BASE = "http://localhost:8800"; | |
const [firstName, setFirstName] = useState(""); | |
const [lastName, setLastName] = useState(""); | |
const [email, setEmail] = useState(""); | |
const [phone, setPhone] = useState(""); | |
const [schedule, setSchedule] = useState([]); | |
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false); | |
const [appointmentDateSelected, setAppointmentDateSelected] = useState(false); | |
const [appointmentMeridiem, setAppointmentMeridiem] = useState(""); | |
const [validEmail, setValidEmail] = useState(true); | |
const [validPhone, setValidPhone] = useState(true); | |
const [finished, setFinished] = useState(false); | |
const [smallScreen, setSmallScreen] = useState(window.innerWidth < 768); | |
const [stepIndex, setStepIndex] = useState(0); | |
const [appointmentDate, setAppointmentDate] = useState(Date.now()); | |
const [appointmentSlot, setAppointmentSlot] = useState(""); | |
const [confirmationTextVisible, setConfirmationTextVisible] = useState(false); | |
const [isLoading, setIsLoading] = useState(false); | |
const [processed, setProcessed] = useState(false); | |
const [confirmationSnackbarOpen, setConfirmationSnackbarOpen] = | |
useState(false); | |
const [confirmationSnackbarMessage, setConfirmationSnackbarMessage] = | |
useState(" "); | |
const fetchData = async () => { | |
const { data } = await axios.get(API_BASE + `/retrieveSlots`); | |
console.log("response via db: ", data); | |
handleDBReponse(data); | |
}; | |
useEffect(() => { | |
fetchData(); | |
}, []); | |
// MOVING THE STEPPER TO THE NEXT AND PREVIOUS POSITION | |
const handleNext = () => { | |
setStepIndex((prev) => prev + 1); | |
setFinished((prev) => prev >= 2); | |
}; | |
const handlePrev = () => { | |
if (stepIndex > 0) { | |
setStepIndex((prev) => prev - 1); | |
} | |
}; | |
// VALIDATING PHONE NUMBER AND EMAILS | |
const validateEmail = (email) => { | |
const regex = | |
/^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\].,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; | |
return regex.test(email) | |
? (setEmail(email), setValidEmail(true)) | |
: setValidEmail(false); | |
}; | |
const validatePhone = (phoneNumber) => { | |
const regex = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/; | |
return regex.test(phoneNumber) | |
? (setPhone(phone), setValidPhone(true)) | |
: this.setState({ validPhone: false }); | |
}; | |
// NEXT, FINISHED AND BACK BUTTONS | |
const renderStepActions = (step) => { | |
return ( | |
<div style={{ margin: "12px 0" }}> | |
<Button | |
variant="contained" | |
label={stepIndex === 2 ? "Finish" : "Next"} | |
disableTouchRipple={true} | |
disableFocusRipple={true} | |
primary={true} | |
onClick={handleNext} | |
backgroundColor="#00C853 !important" | |
style={{ marginRight: 12, backgroundColor: "#00C853" }} | |
> | |
Continue | |
</Button> | |
{step > 0 && ( | |
<Button | |
label="Back" | |
disabled={stepIndex === 0} | |
disableTouchRipple={true} | |
disableFocusRipple={true} | |
onClick={handlePrev} | |
style={{ marginRight: 12, backgroundColor: "#d8C893" }} | |
> | |
Back | |
</Button> | |
)} | |
</div> | |
); | |
}; | |
// set state of the appointmentDate field | |
const handleSetAppointmentDate = (date) => { | |
setAppointmentDate(date); | |
setConfirmationTextVisible(true); | |
setValue(date); | |
}; | |
// set state of the appointment slot visible | |
const handleSetAppointmentSlot = (event) => { | |
setAppointmentSlot(event.target.value); | |
}; | |
// set the state for the appointmentMeridiem field | |
const handleSetAppointmentMeridiem = (event) => { | |
setAppointmentMeridiem(event.target.value); | |
}; | |
// PASS DATES TO THE DATE PICKER COMPONENT | |
const checkDisableDate = (day) => { | |
const dateString = moment(day).format("YYYY-DD-MM"); | |
return ( | |
schedule[dateString] === true || | |
moment(day).startOf("day").diff(moment().startOf("day")) < 0 | |
); | |
}; | |
// HANDLE THE APPOINTMENT SLOT DATA FROM THE DATABASE | |
const handleDBReponse = (response) => { | |
const appointments = response; | |
const today = moment().startOf("day"); //start of today 12 am | |
const initialSchedule = {}; | |
initialSchedule[today.format("YYYY-DD-MM")] = true; | |
const schedule = !appointments.length | |
? initialSchedule | |
: appointments.reduce((currentSchedule, appointment) => { | |
const { slot_date, slot_time } = appointment; | |
const dateString = moment(slot_date, "YYYY-DD-MM").format( | |
"YYYY-DD-MM" | |
); | |
!currentSchedule[slot_date] | |
? (currentSchedule[dateString] = Array(8).fill(false)) | |
: null; | |
Array.isArray(currentSchedule[dateString]) | |
? (currentSchedule[dateString][slot_time] = true) | |
: null; | |
return currentSchedule; | |
}, initialSchedule); | |
for (let day in schedule) { | |
let slots = schedule[day]; | |
slots.length | |
? slots.every((slot) => slot === true) | |
? (schedule[day] = true) | |
: null | |
: null; | |
} | |
setSchedule(schedule); | |
}; | |
// DISPLAY THE AVAILABLE TIME SLOTS AND DISABLE THE REST | |
const renderAppointmentTimes = () => { | |
// if (!isLoading) { | |
if (true) { | |
const slotss = [...Array(8).keys()]; | |
// const slots = ["8am - 10am", "10am - 12pm"]; | |
const slots = slotss.filter(function (mov) { | |
return mov % 2 !== 0; | |
}); | |
console.log("slotssssssssssssssssss", slotss); | |
console.log("slotssssssssssssssssss", slots); | |
return slots.map((slot) => { | |
// slot = slot + 1; | |
console.log( | |
"Monemt appointment date ???????????????????????????", | |
moment(appointmentDate).format("YYYY-DD-MM") | |
); | |
const appointmentDateString = | |
moment(appointmentDate).format("YYYY-DD-MM"); | |
console.log( | |
"appointmentDateString>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", | |
appointmentDateString | |
); | |
const time1 = moment() | |
.hour(6) | |
.minute(0) | |
.add(slot + 2, "hours"); | |
// const time2 = moment() | |
// .hour(8) | |
// .minute(0) | |
// .add(slot + 2, "hours"); | |
const scheduleDisabled = schedule[appointmentDateString] | |
? schedule[moment(appointmentDate).format("YYYY-DD-MM")][slot] | |
: false; | |
return ( | |
<FormControlLabel | |
label={time1.format("h:mm a")} | |
// label={time1.format("h:mm a") + " - " + time2.format("h:mm a")} | |
key={slot} | |
value={slot} | |
control={<Radio />} | |
style={{ | |
marginBottom: 15, | |
// display: meridiemDisabled ? "none" : "inherit", | |
}} | |
disabled={scheduleDisabled} | |
/> | |
); | |
}); | |
} else { | |
return null; | |
} | |
}; | |
// DISPLAY USER INFORMATION CONFIRMATION BEFORE SAVING TO DATABASE | |
const renderAppointmentConfirmation = () => { | |
console.log("firstname>>>>>>>>>>>>>>>>>>>>>", firstName); | |
const spanStyle = { color: "#00C853" }; | |
return ( | |
<section> | |
<p> | |
Name: {firstName} | |
{/* <span style={spanStyle}> | |
{firstName} {lastName} | |
</span> */} | |
</p> | |
<p> | |
Number: <span style={spanStyle}>{phone}</span> | |
</p> | |
<p> | |
Email: <span style={spanStyle}>{email}</span> | |
</p> | |
<p> | |
Appointment:{" "} | |
<span style={spanStyle}> | |
{moment(appointmentDate).format("dddd[,] MMMM Do[,] YYYY")} | |
</span>{" "} | |
at{" "} | |
<span style={spanStyle}> | |
{moment() | |
.hour(9) | |
.minute(0) | |
.add(appointmentSlot, "hours") | |
.format("h:mm a")} | |
</span> | |
</p> | |
</section> | |
); | |
}; | |
// PASS DATA TO DATABASE - EXPRESS APP, DISPLAY SUCCESS/FAIL MESSAGE | |
const handleSubmit = () => { | |
setOpen(false); | |
const newAppointment = { | |
name: firstName + " " + lastName, | |
email: email, | |
phone: phone, | |
slot_date: moment(appointmentDate).format("YYYY-DD-MM"), | |
slot_time: appointmentSlot, | |
}; | |
axios | |
.post(API_BASE + "/appointmentCreate", newAppointment) | |
.then((response) => { | |
console.log(response); | |
setConfirmationSnackbarMessage("Appointment succesfully added!"); | |
setConfirmationSnackbarOpen(true); | |
setProcessed(true); | |
// setIsLoading(true); | |
}) | |
.catch((err) => { | |
console.log(err); | |
// ( setConfirmationSnackbarMessage("Appointment failed to save "), | |
// setConfirmationSnackbarOpen(true)); | |
}); | |
}; | |
// RENDERING ................................ | |
// const [...data] = useState; | |
let contactFormFilled; | |
if (firstName !== "" && lastName !== "" && phone !== "" && email !== "") { | |
contactFormFilled = true; | |
} else { | |
contactFormFilled = false; | |
} | |
// && validPhone && validEmail; | |
const [value, setValue] = useState(""); | |
const [timeOfDay, setTimeOfDay] = useState(""); | |
const Transition = React.forwardRef(function Transition(props, ref) { | |
return <Slide direction="up" ref={ref} {...props} />; | |
}); | |
const [open, setOpen] = React.useState(false); | |
const handleClickOpen = () => { | |
setOpen(true); | |
}; | |
const handleClose = () => { | |
setOpen(false); | |
}; | |
const DatePickerExampleSimple = () => ( | |
<div> | |
{/* <DatePicker | |
label="Select Date" | |
mode={smallScreen ? "portrait" : "landscape"} | |
onChange={(n, date) => handleSetAppointmentDate(date)} | |
shouldDisableDate={(day) => checkDisableDate(day)} | |
/> */} | |
<DatePicker | |
label="Select Date" | |
value={value} | |
// onChange={(newValue) => { | |
// setValue(newValue); | |
// }} | |
onChange={(date) => handleSetAppointmentDate(date)} | |
shouldDisableDate={(day) => checkDisableDate(day)} | |
renderInput={(params) => <TextField {...params} />} | |
/> | |
</div> | |
); | |
const modalActions = [ | |
// FlatButton | |
<Button label="Cancel" primary={false} onClick={() => setOpen(false)} />, | |
<Button | |
label="Confirm" | |
style={{ backgroundColor: "#00C853 !important" }} | |
primary={true} | |
onClick={() => handleSubmit()} | |
/>, | |
]; | |
return ( | |
<div> | |
{/* <AppBar | |
title="Appointment Scheduler" | |
iconClassNameRight="muidocs-icon-navigation-expand-more" | |
/> */} | |
<AppBar position="static"> | |
<Toolbar variant="dense"> | |
<IconButton | |
edge="start" | |
color="inherit" | |
aria-label="menu" | |
sx={{ mr: 2 }} | |
> | |
<h1>Icon</h1> | |
</IconButton> | |
<Typography variant="h6" color="inherit" component="div"> | |
Booking | |
</Typography> | |
</Toolbar> | |
</AppBar> | |
<section | |
style={{ | |
maxWidth: !smallScreen ? "80%" : "100%", | |
margin: "auto", | |
marginTop: !smallScreen ? 20 : 0, | |
}} | |
> | |
<Card | |
style={{ | |
padding: "12px 12px 25px 12px", | |
height: smallScreen ? "100vh" : null, | |
}} | |
> | |
<Stepper | |
activeStep={stepIndex} | |
// alternativeLabel | |
orientation="vertical" | |
// orientation="vertical" linear={false} | |
> | |
<Step> | |
<StepLabel> | |
Choose an available day for your appointment | |
</StepLabel> | |
<StepContent> | |
{DatePickerExampleSimple()} | |
{renderStepActions(0)} | |
</StepContent> | |
</Step> | |
{/* check for date disable */} | |
<Step disabled={!appointmentDate}> | |
<StepLabel> | |
Choose an available time for your appointment | |
</StepLabel> | |
<StepContent> | |
{/* <Box sx={{ minWidth: 100 }}> | |
<FormControl fullWidth> | |
<InputLabel id="demo-simple-select-label">Time</InputLabel> | |
<Select | |
value={appointmentMeridiem} | |
label="Time" | |
onChange={handleSetAppointmentMeridiem} | |
// selectionRenderer={(value) => (value ? "PM" : "AM")} | |
> | |
<MenuItem value={1}> AM</MenuItem> | |
<MenuItem value={2}> PM</MenuItem> | |
</Select> | |
</FormControl> | |
</Box> */} | |
{/* ///////////////////////////////// */} | |
<FormControl> | |
{/* <FormLabel id="demo-radio-buttons-group-label"> | |
Choose Appointment Slot | |
</FormLabel> */} | |
<RadioGroup | |
style={{ | |
marginTop: 15, | |
marginLeft: 15, | |
}} | |
name="appointmentTimes" | |
// defaultSelected={appointmentSlot} | |
onChange={handleSetAppointmentSlot} | |
> | |
{renderAppointmentTimes()} | |
</RadioGroup> | |
</FormControl> | |
{renderStepActions(1)} | |
</StepContent> | |
</Step> | |
<Step> | |
<StepLabel>contact information/Booking Details</StepLabel> | |
<StepContent> | |
<Box | |
component="form" | |
sx={{ | |
"& .MuiTextField-root": { m: 1, width: "55ch" }, | |
}} | |
noValidate | |
autoComplete="off" | |
> | |
<div> | |
<TextField | |
// style={{ display: "block" }} | |
name="first_name" | |
label="FirstName" | |
multiline | |
maxRows={4} | |
id="outlined-error" | |
placeholder="First Name" | |
onChange={(event) => setFirstName(event.target.value)} | |
/> | |
<TextField | |
// style={{ display: "block" }} | |
name="last_name" | |
label="Last Name" | |
multiline | |
placeholder="Last Name" | |
maxRows={4} | |
onChange={(event) => setLastName(event.target.value)} | |
/> | |
</div> | |
<div> | |
<TextField | |
name="email" | |
placeholder="[email protected]" | |
multiline | |
maxRows={4} | |
label="Email" | |
id="outlined-error" | |
// error | |
// helperText={ | |
// validEmail ? null : "Enter a valid email address" | |
// } | |
onChange={(event) => setEmail(event.target.value)} | |
/> | |
<TextField | |
name="phone" | |
placeholder="+2547******34/07******34" | |
label="Phone" | |
// errorText={ | |
// validPhone ? null : "Enter a valid phone number" | |
// } | |
onChange={(event) => setPhone(event.target.value)} | |
/> | |
</div> | |
<Button | |
style={{ display: "block", backgroundColor: "#00C853" }} | |
variant="raised" | |
// variant={ | |
// contactFormFilled | |
// ? "Schedule" | |
// : "Fill out your information to schedule" | |
// } | |
labelPosition="before" | |
primary={true} | |
fullWidth={true} | |
onClick={() => setOpen(!open)} | |
disabled={!contactFormFilled || processed} | |
// style={{ marginTop: 20, maxWidth: 100 }} | |
> | |
{contactFormFilled | |
? "Confirm your Info" | |
: "Fill out your information to schedule"} | |
</Button> | |
</Box> | |
{renderStepActions(2)} | |
</StepContent> | |
</Step> | |
</Stepper> | |
</Card> | |
{/* //////////////////////// */} | |
<Dialog | |
TransitionComponent={Transition} | |
open={open} | |
keepMounted | |
onClose={handleClose} | |
// actions={modalActions} | |
// title="Confirm your appointment" | |
aria-describedby="Confirm your appointment" | |
> | |
<DialogContent>{renderAppointmentConfirmation()}</DialogContent> | |
<DialogActions> | |
<Button | |
variant="outlined" | |
label="Cancel" | |
primary={false} | |
onClick={() => setOpen(false)} | |
> | |
{" "} | |
Cancel{" "} | |
</Button> | |
, | |
<Button | |
variant="outlined" | |
// label="Confirm" | |
style={{ backgroundColor: "#00C853 !important" }} | |
// primary={true} | |
onClick={() => handleSubmit()} | |
> | |
{" "} | |
Confirm{" "} | |
</Button> | |
, | |
</DialogActions> | |
</Dialog> | |
<Snackbar | |
open={confirmationSnackbarOpen || isLoading} | |
message={ | |
isLoading ? "Loading... " : confirmationSnackbarMessage || "" | |
} | |
autoHideDuration={10000} | |
// onRequestClose={() => | |
// this.setState({ confirmationSnackbarOpen: false }) | |
// setConfirmationSnackbarOpen(false) | |
// } | |
/> | |
</section> | |
</div> | |
); | |
}; | |
export default AppointmentApp; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment