mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-18 13:34:06 +01:00
feat: disable non-freetime and overlap time for meeting
This commit is contained in:
parent
11ecd3e16f
commit
8136c7c1b2
@ -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}>
|
||||
|
||||
36
src/app/calendar/overlapEvent.ts
Normal file
36
src/app/calendar/overlapEvent.ts
Normal 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;
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user