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 { Input } from "@/components/ui/input";
|
||||||
import { Clock } from "lucide-react";
|
import { Clock } from "lucide-react";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { createCalendarEvent, createMeetingLog } from "./actions";
|
import { createCalendarEvent, createMeetingLog, getFreeDate } from "./actions";
|
||||||
import { Session } from "@supabase/supabase-js";
|
import { Session } from "@supabase/supabase-js";
|
||||||
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
|
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
|
||||||
import { TimeInput } from "@nextui-org/date-input";
|
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 { TimeValue } from "@react-types/datepicker";
|
||||||
import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";
|
import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";
|
||||||
|
// import { useLocale } from "@react-aria/i18n";
|
||||||
|
import { getMeetingLog } from "./actions";
|
||||||
import toast from "react-hot-toast";
|
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 {
|
interface DialogProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -32,7 +37,7 @@ interface DialogProps {
|
|||||||
modal?: boolean;
|
modal?: boolean;
|
||||||
session: Session;
|
session: Session;
|
||||||
projectName: string;
|
projectName: string;
|
||||||
projectId?: number;
|
projectId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MeetEventDialog(props: DialogProps) {
|
export function MeetEventDialog(props: DialogProps) {
|
||||||
@ -49,12 +54,24 @@ export function MeetEventDialog(props: DialogProps) {
|
|||||||
const [noteToBusiness, setNoteToBusiness] = useState<string>("");
|
const [noteToBusiness, setNoteToBusiness] = useState<string>("");
|
||||||
const session = props.session;
|
const session = props.session;
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: freeDate,
|
||||||
|
error: freeDateError,
|
||||||
|
isLoading: isLoadingFreeDate,
|
||||||
|
} = useQuery(getFreeDate(supabase, props.projectId), { enabled: !!props.projectId });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.projectName) {
|
if (props.projectName) {
|
||||||
setEventName(`Meet with ${props.projectName}`);
|
setEventName(`Meet with ${props.projectName}`);
|
||||||
}
|
}
|
||||||
}, [props.projectName]);
|
}, [props.projectName]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: meetingLog,
|
||||||
|
error: meetingLogError,
|
||||||
|
isLoading: isLoadingMeetingLog,
|
||||||
|
} = useQuery(getMeetingLog(supabase, props.projectId), { enabled: !!props.projectId });
|
||||||
|
|
||||||
const handleCreateEvent = async () => {
|
const handleCreateEvent = async () => {
|
||||||
if (!session || !eventDate || !startTime || !endTime || !eventName) {
|
if (!session || !eventDate || !startTime || !endTime || !eventName) {
|
||||||
toast.error("Please fill in all event details.");
|
toast.error("Please fill in all event details.");
|
||||||
@ -70,6 +87,18 @@ export function MeetEventDialog(props: DialogProps) {
|
|||||||
const endDate = eventDate.toDate(timezone);
|
const endDate = eventDate.toDate(timezone);
|
||||||
endDate.setHours(endTime.hour, startTime.minute);
|
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);
|
await createCalendarEvent(session, startDate, endDate, eventName, eventDescription);
|
||||||
|
|
||||||
const { status, error } = await createMeetingLog({
|
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 (
|
return (
|
||||||
<Dialog {...props}>
|
<Dialog {...props}>
|
||||||
<DialogContent className="sm:max-w-md overflow-y-auto h-[80%]">
|
<DialogContent className="sm:max-w-md overflow-y-auto h-[80%]">
|
||||||
@ -111,49 +167,62 @@ export function MeetEventDialog(props: DialogProps) {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4 w-[90%]">
|
{meetingLogError || freeDateError ? (
|
||||||
<div>
|
<div className="error-message">
|
||||||
<Label htmlFor="eventName">Event Name</Label>
|
<p>Error loading meeting logs</p>
|
||||||
<Input
|
|
||||||
id="eventName"
|
|
||||||
placeholder="Enter event name"
|
|
||||||
value={eventName}
|
|
||||||
onChange={(e) => setEventName(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
) : !isLoadingMeetingLog || !isLoadingFreeDate ? (
|
||||||
<Label htmlFor="eventDescription">Event Description</Label>
|
<div className="space-y-4 w-[90%]">
|
||||||
<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>
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Start Time</Label>
|
<Label htmlFor="eventName">Event Name</Label>
|
||||||
<TimeInput label="Start Time" value={startTime} onChange={setStartTime} />
|
<Input
|
||||||
|
id="eventName"
|
||||||
|
placeholder="Enter event name"
|
||||||
|
value={eventName}
|
||||||
|
onChange={(e) => setEventName(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>End Time</Label>
|
<Label htmlFor="eventDescription">Event Description</Label>
|
||||||
<TimeInput label="End Time" value={endTime} onChange={setEndTime} />
|
<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>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<LegacyLoader />
|
||||||
|
)}
|
||||||
|
|
||||||
<DialogFooter className="sm:justify-start mt-4">
|
<DialogFooter className="sm:justify-start mt-4">
|
||||||
<Button type="button" onClick={handleCreateEvent} className="mr-2" disabled={isSubmitting}>
|
<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>
|
</div>
|
||||||
<MeetEventDialog
|
<MeetEventDialog
|
||||||
open={showModal}
|
open={showModal}
|
||||||
onOpenChange={setShowModal}
|
onOpenChange={(open) => {
|
||||||
|
setShowModal(open);
|
||||||
|
if (!open) {
|
||||||
|
setCurrentProjectId(undefined);
|
||||||
|
setCurrentProjectName("");
|
||||||
|
}
|
||||||
|
}}
|
||||||
session={session}
|
session={session}
|
||||||
projectName={currentProjectName}
|
projectName={currentProjectName}
|
||||||
projectId={currentProjectId}
|
projectId={currentProjectId!}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user