cal.pub0.org/components/ui/Scheduler.tsx

141 lines
4.5 KiB
TypeScript

import { TrashIcon } from "@heroicons/react/outline";
import { Availability } from "@prisma/client";
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import React, { useEffect, useState } from "react";
import TimezoneSelect from "react-timezone-select";
import { useLocale } from "@lib/hooks/useLocale";
import { WeekdaySelect } from "./WeekdaySelect";
import SetTimesModal from "./modal/SetTimesModal";
dayjs.extend(utc);
dayjs.extend(timezone);
type Props = {
timeZone: string;
availability: Availability[];
setTimeZone: unknown;
};
export const Scheduler = ({
availability,
setAvailability,
timeZone: selectedTimeZone,
setTimeZone,
}: Props) => {
const { t } = useLocale();
const [editSchedule, setEditSchedule] = useState(-1);
const [dateOverrides, setDateOverrides] = useState([]);
const [openingHours, setOpeningHours] = useState([]);
useEffect(() => {
setOpeningHours(
availability
.filter((item: Availability) => item.days.length !== 0)
.map((item) => {
item.startDate = dayjs().utc().startOf("day").add(item.startTime, "minutes");
item.endDate = dayjs().utc().startOf("day").add(item.endTime, "minutes");
return item;
})
);
setDateOverrides(availability.filter((item: Availability) => item.date));
}, []);
// updates availability to how it should be formatted outside this component.
useEffect(() => {
setAvailability({
dateOverrides: dateOverrides,
openingHours: openingHours,
});
}, [dateOverrides, openingHours]);
const addNewSchedule = () => setEditSchedule(openingHours.length);
const applyEditSchedule = (changed) => {
// new entry
if (!changed.days) {
changed.days = [1, 2, 3, 4, 5]; // Mon - Fri
setOpeningHours(openingHours.concat(changed));
} else {
// update
const replaceWith = { ...openingHours[editSchedule], ...changed };
openingHours.splice(editSchedule, 1, replaceWith);
setOpeningHours([].concat(openingHours));
}
};
const removeScheduleAt = (toRemove: number) => {
openingHours.splice(toRemove, 1);
setOpeningHours([].concat(openingHours));
};
const OpeningHours = ({ idx, item }) => (
<li className="py-2 flex justify-between border-b">
<div className="flex flex-col space-y-4 lg:inline-flex">
<WeekdaySelect defaultValue={item.days} onSelect={(selected: number[]) => (item.days = selected)} />
<button
className="text-sm bg-neutral-100 rounded-sm py-2 px-3"
type="button"
onClick={() => setEditSchedule(idx)}>
{dayjs()
.startOf("day")
.add(item.startTime, "minutes")
.format(item.startTime % 60 === 0 ? "ha" : "h:mma")}
&nbsp;{t("until")}&nbsp;
{dayjs()
.startOf("day")
.add(item.endTime, "minutes")
.format(item.endTime % 60 === 0 ? "ha" : "h:mma")}
</button>
</div>
<button
type="button"
onClick={() => removeScheduleAt(idx)}
className="btn-sm bg-transparent px-2 py-1 ml-1">
<TrashIcon className="h-5 w-5 inline text-gray-400 -mt-1" />
</button>
</li>
);
return (
<div>
<div className="flex">
<div className="w-full">
<div>
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
{t("timezone")}
</label>
<div className="mt-1">
<TimezoneSelect
id="timeZone"
value={{ value: selectedTimeZone }}
onChange={(tz) => setTimeZone(tz.value)}
className="shadow-sm focus:ring-black focus:border-black mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
/>
</div>
</div>
<ul>
{openingHours.map((item, idx) => (
<OpeningHours key={idx} idx={idx} item={item} />
))}
</ul>
<button type="button" onClick={addNewSchedule} className="btn-white btn-sm mt-2">
{t("add_another")}
</button>
</div>
</div>
{editSchedule >= 0 && (
<SetTimesModal
startTime={openingHours[editSchedule] ? openingHours[editSchedule].startTime : 540}
endTime={openingHours[editSchedule] ? openingHours[editSchedule].endTime : 1020}
onChange={(times) => applyEditSchedule({ ...(openingHours[editSchedule] || {}), ...times })}
onExit={() => setEditSchedule(-1)}
/>
)}
</div>
);
};