cal.pub0.org/apps/web/components/ui/modal/SetTimesModal.tsx

239 lines
9.6 KiB
TypeScript

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">
&#8203;
</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>
);
}