import { ClockIcon } from "@heroicons/react/outline"; import dayjs from "dayjs"; import customParseFormat from "dayjs/plugin/customParseFormat"; import { useRef, useState } from "react"; import { useLocale } from "@lib/hooks/useLocale"; import showToast from "@lib/notification"; import Button from "@components/ui/Button"; dayjs.extend(customParseFormat); interface SetTimesModalProps { startTime: number; endTime: number; onChange: (times: { startTime: number; endTime: number }) => void; onExit: (...p: unknown[]) => void; } export default function SetTimesModal(props: SetTimesModalProps) { const { t } = useLocale(); const [startHours, startMinutes] = [Math.floor(props.startTime / 60), props.startTime % 60]; const [endHours, endMinutes] = [Math.floor(props.endTime / 60), props.endTime % 60]; const startHoursRef = useRef<HTMLInputElement>(null!); const startMinsRef = useRef<HTMLInputElement>(null!); const endHoursRef = useRef<HTMLInputElement>(null!); const endMinsRef = useRef<HTMLInputElement>(null!); const [endMinuteDisable, setEndMinuteDisable] = useState(false); const [maximumStartTime, setMaximumStartTime] = useState({ hour: endHours, minute: 59 }); const [minimumEndTime, setMinimumEndTime] = useState({ hour: startHours, minute: 59 }); const STEP = 15; const isValidTime = (startTime: number, endTime: number) => { if (new Date(startTime) > new Date(endTime)) { showToast(t("error_end_time_before_start_time"), "error"); return false; } if (endTime > 1440) { showToast(t("error_end_time_next_day"), "error"); return false; } return true; }; // compute dynamic range for minimum and maximum allowed hours/minutes. const setEdgeTimes = ( (step) => ( startHoursRef: React.MutableRefObject<HTMLInputElement>, startMinsRef: React.MutableRefObject<HTMLInputElement>, endHoursRef: React.MutableRefObject<HTMLInputElement>, endMinsRef: React.MutableRefObject<HTMLInputElement> ) => { //parse all the refs const startHour = parseInt(startHoursRef.current.value); let startMinute = parseInt(startMinsRef.current.value); const endHour = parseInt(endHoursRef.current.value); let endMinute = parseInt(endMinsRef.current.value); //convert to dayjs object const startTime = dayjs(`${startHour}:${startMinute}`, "hh:mm"); const endTime = dayjs(`${endHour}:${endMinute}`, "hh:mm"); //compute minimin and maximum allowed const maximumStartTime = endTime.subtract(step, "minute"); const maximumStartHour = maximumStartTime.hour(); const maximumStartMinute = startHour === endHour ? maximumStartTime.minute() : 59; const minimumEndTime = startTime.add(step, "minute"); const minimumEndHour = minimumEndTime.hour(); const minimumEndMinute = startHour === endHour ? minimumEndTime.minute() : 0; //check allow min/max minutes when the end/start hour matches if (startHoursRef.current.value === endHoursRef.current.value) { if (parseInt(startMinsRef.current.value) >= maximumStartMinute) startMinsRef.current.value = maximumStartMinute.toString(); if (parseInt(endMinsRef.current.value) <= minimumEndMinute) endMinsRef.current.value = minimumEndMinute.toString(); } //save into state setMaximumStartTime({ hour: maximumStartHour, minute: maximumStartMinute }); setMinimumEndTime({ hour: minimumEndHour, minute: minimumEndMinute }); } )(STEP); return ( <div className="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true"> <div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0"> <div className="fixed inset-0 z-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div> <span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true"> ​ </span> <div className="inline-block transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 sm:align-middle"> <div className="mb-4 sm:flex sm:items-start"> <div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> <ClockIcon className="h-6 w-6 text-black" /> </div> <div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title"> {t("change_bookings_availability")} </h3> <div> <p className="text-sm text-gray-500">{t("set_work_schedule")}</p> </div> </div> </div> <div className="mb-4 flex"> <label className="block w-1/4 pt-2 text-sm font-medium text-gray-700">{t("start_time")}</label> <div className="w-1/6"> <label htmlFor="startHours" className="sr-only"> {t("hours")} </label> <input ref={startHoursRef} type="number" min="0" max={maximumStartTime.hour} minLength={2} name="hours" id="startHours" className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm" placeholder="9" defaultValue={startHours} onChange={() => setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef)} /> </div> <span className="mx-2 pt-1">:</span> <div className="w-1/6"> <label htmlFor="startMinutes" className="sr-only"> {t("minutes")} </label> <input ref={startMinsRef} type="number" min="0" max={maximumStartTime.minute} step={STEP} maxLength={2} name="minutes" id="startMinutes" className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm" placeholder="30" defaultValue={startMinutes} onChange={() => setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef)} /> </div> </div> <div className="flex"> <label className="block w-1/4 pt-2 text-sm font-medium text-gray-700">{t("end_time")}</label> <div className="w-1/6"> <label htmlFor="endHours" className="sr-only"> {t("hours")} </label> <input ref={endHoursRef} type="number" min={minimumEndTime.hour} max="24" maxLength={2} name="hours" id="endHours" className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm" placeholder="17" defaultValue={endHours} onChange={(e) => { if (endHoursRef.current.value === "24") endMinsRef.current.value = "0"; setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef); setEndMinuteDisable(endHoursRef.current.value === "24"); }} /> </div> <span className="mx-2 pt-1">:</span> <div className="w-1/6"> <label htmlFor="endMinutes" className="sr-only"> {t("minutes")} </label> <input ref={endMinsRef} type="number" min={minimumEndTime.minute} max="59" maxLength={2} step={STEP} name="minutes" id="endMinutes" className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm" placeholder="30" defaultValue={endMinutes} disabled={endMinuteDisable} onChange={() => setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef)} /> </div> </div> <div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> <Button onClick={(event) => { event.preventDefault(); const enteredStartHours = parseInt(startHoursRef.current.value); const enteredStartMins = parseInt(startMinsRef.current.value); const enteredEndHours = parseInt(endHoursRef.current.value); const enteredEndMins = parseInt(endMinsRef.current.value); if ( isValidTime( enteredStartHours * 60 + enteredStartMins, enteredEndHours * 60 + enteredEndMins ) ) { props.onChange({ startTime: enteredStartHours * 60 + enteredStartMins, endTime: enteredEndHours * 60 + enteredEndMins, }); props.onExit(0); } }} type="submit"> {t("save")} </Button> <Button onClick={props.onExit} type="button" color="secondary" className="ltr:mr-2"> {t("cancel")} </Button> </div> </div> </div> </div> ); }