feat: disable non-freetime and overlap time for meeting

This commit is contained in:
Sosokker 2024-11-15 16:16:40 +07:00
parent 11ecd3e16f
commit 8136c7c1b2
3 changed files with 153 additions and 42 deletions

View File

@ -14,14 +14,19 @@ import {
import { Input } from "@/components/ui/input";
import { Clock } from "lucide-react";
import { Label } from "@/components/ui/label";
import { createCalendarEvent, createMeetingLog } from "./actions";
import { createCalendarEvent, createMeetingLog, getFreeDate } from "./actions";
import { Session } from "@supabase/supabase-js";
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
import { TimeInput } from "@nextui-org/date-input";
import { Calendar } from "@nextui-org/calendar";
import { Calendar, DateValue } from "@nextui-org/calendar";
import { TimeValue } from "@react-types/datepicker";
import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";
// import { useLocale } from "@react-aria/i18n";
import { getMeetingLog } from "./actions";
import toast from "react-hot-toast";
import { useQuery } from "@supabase-cache-helpers/postgrest-react-query";
import { LegacyLoader } from "@/components/loading/LegacyLoader";
import { isEventOverlapping } from "./overlapEvent";
interface DialogProps {
children?: React.ReactNode;
@ -32,7 +37,7 @@ interface DialogProps {
modal?: boolean;
session: Session;
projectName: string;
projectId?: number;
projectId: number;
}
export function MeetEventDialog(props: DialogProps) {
@ -49,12 +54,24 @@ export function MeetEventDialog(props: DialogProps) {
const [noteToBusiness, setNoteToBusiness] = useState<string>("");
const session = props.session;
const {
data: freeDate,
error: freeDateError,
isLoading: isLoadingFreeDate,
} = useQuery(getFreeDate(supabase, props.projectId), { enabled: !!props.projectId });
useEffect(() => {
if (props.projectName) {
setEventName(`Meet with ${props.projectName}`);
}
}, [props.projectName]);
const {
data: meetingLog,
error: meetingLogError,
isLoading: isLoadingMeetingLog,
} = useQuery(getMeetingLog(supabase, props.projectId), { enabled: !!props.projectId });
const handleCreateEvent = async () => {
if (!session || !eventDate || !startTime || !endTime || !eventName) {
toast.error("Please fill in all event details.");
@ -70,6 +87,18 @@ export function MeetEventDialog(props: DialogProps) {
const endDate = eventDate.toDate(timezone);
endDate.setHours(endTime.hour, startTime.minute);
const existingEvents = (meetingLog || []).map((log) => ({
meet_date: log.meet_date,
start_time: log.start_time,
end_time: log.end_time,
}));
const hasOverlap = isEventOverlapping(eventDate, startTime, endTime, existingEvents);
if (hasOverlap) {
toast.error("This current selected date and time is overlaped with any existing events.");
return;
}
await createCalendarEvent(session, startDate, endDate, eventName, eventDescription);
const { status, error } = await createMeetingLog({
@ -98,6 +127,33 @@ export function MeetEventDialog(props: DialogProps) {
}
};
const meetingLogRanges = (meetingLog || []).map((log) => {
const [year, month, day] = log.meet_date.split("-").map(Number);
const startDate = new CalendarDate(year, month, day);
const endDate = new CalendarDate(year, month, day);
return [startDate, endDate];
});
const freetimeLogRanges = (freeDate || []).map((log) => {
const [year, month, day] = log.meet_date.split("-").map(Number);
const startDate = new CalendarDate(year, month, day);
const endDate = new CalendarDate(year, month, day);
return [startDate, endDate];
});
// const disabledRanges = [...meetingLogRanges];
// const { locale } = useLocale();
const isDateUnavailable = (date: DateValue) => {
const isFreeDate = freetimeLogRanges.some(
(interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0
);
const isMeetingDate = meetingLogRanges.some(
(interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0
);
return !isFreeDate || isMeetingDate;
};
return (
<Dialog {...props}>
<DialogContent className="sm:max-w-md overflow-y-auto h-[80%]">
@ -111,49 +167,62 @@ export function MeetEventDialog(props: DialogProps) {
</DialogDescription>
</DialogHeader>
<div className="space-y-4 w-[90%]">
<div>
<Label htmlFor="eventName">Event Name</Label>
<Input
id="eventName"
placeholder="Enter event name"
value={eventName}
onChange={(e) => setEventName(e.target.value)}
/>
{meetingLogError || freeDateError ? (
<div className="error-message">
<p>Error loading meeting logs</p>
</div>
<div>
<Label htmlFor="eventDescription">Event Description</Label>
<Input
id="eventDescription"
placeholder="Enter event description"
value={eventDescription}
onChange={(e) => setEventDescription(e.target.value)}
/>
</div>
<div>
<Label htmlFor="eventDescription">Event Description</Label>
<Input
id="note"
placeholder="Your note to business"
value={noteToBusiness}
onChange={(e) => setNoteToBusiness(e.target.value)}
/>
</div>
<div>
<Label>Date</Label>
<Calendar value={eventDate} onChange={setEventDate} minValue={today(getLocalTimeZone())} />
</div>
<div>
) : !isLoadingMeetingLog || !isLoadingFreeDate ? (
<div className="space-y-4 w-[90%]">
<div>
<Label>Start Time</Label>
<TimeInput label="Start Time" value={startTime} onChange={setStartTime} />
<Label htmlFor="eventName">Event Name</Label>
<Input
id="eventName"
placeholder="Enter event name"
value={eventName}
onChange={(e) => setEventName(e.target.value)}
/>
</div>
<div>
<Label>End Time</Label>
<TimeInput label="End Time" value={endTime} onChange={setEndTime} />
<Label htmlFor="eventDescription">Event Description</Label>
<Input
id="eventDescription"
placeholder="Enter event description"
value={eventDescription}
onChange={(e) => setEventDescription(e.target.value)}
/>
</div>
<div>
<Label htmlFor="eventDescription">Event Description</Label>
<Input
id="note"
placeholder="Your note to business"
value={noteToBusiness}
onChange={(e) => setNoteToBusiness(e.target.value)}
/>
</div>
<div className="flex flex-col space-y-2">
<Label>Date</Label>
<Calendar
value={eventDate}
onChange={setEventDate}
minValue={today(getLocalTimeZone())}
isDateUnavailable={isDateUnavailable}
/>
</div>
<div>
<div>
<Label>Start Time</Label>
<TimeInput label="Start Time" value={startTime} onChange={setStartTime} />
</div>
<div>
<Label>End Time</Label>
<TimeInput label="End Time" value={endTime} onChange={setEndTime} />
</div>
</div>
</div>
</div>
) : (
<LegacyLoader />
)}
<DialogFooter className="sm:justify-start mt-4">
<Button type="button" onClick={handleCreateEvent} className="mr-2" disabled={isSubmitting}>

View File

@ -0,0 +1,36 @@
import { CalendarDate, getLocalTimeZone } from "@internationalized/date";
import { TimeValue } from "@react-types/datepicker";
export function isEventOverlapping(
eventDate: CalendarDate,
startTime: TimeValue,
endTime: TimeValue,
existingEvents: { meet_date: string; start_time: string; end_time: string }[]
): boolean {
const timezone = getLocalTimeZone();
const newStartDate = eventDate.toDate(timezone);
newStartDate.setHours(startTime.hour, startTime.minute);
const newEndDate = eventDate.toDate(timezone);
newEndDate.setHours(endTime.hour, endTime.minute);
for (const log of existingEvents) {
const [year, month, day] = log.meet_date.split("-").map(Number);
const existingStartDate = new Date(year, month - 1, day);
existingStartDate.setHours(parseInt(log.start_time.split(":")[0]), parseInt(log.start_time.split(":")[1]));
const existingEndDate = new Date(year, month - 1, day);
existingEndDate.setHours(parseInt(log.end_time.split(":")[0]), parseInt(log.end_time.split(":")[1]));
const isOverlapping =
(newStartDate < existingEndDate && newEndDate > existingStartDate) ||
(existingStartDate < newEndDate && existingEndDate > newStartDate);
if (isOverlapping) {
return true;
}
}
return false;
}

View File

@ -138,10 +138,16 @@ const DatetimePickerHourCycle = () => {
</div>
<MeetEventDialog
open={showModal}
onOpenChange={setShowModal}
onOpenChange={(open) => {
setShowModal(open);
if (!open) {
setCurrentProjectId(undefined);
setCurrentProjectName("");
}
}}
session={session}
projectName={currentProjectName}
projectId={currentProjectId}
projectId={currentProjectId!}
/>
</div>
);