diff --git a/src/app/calendar/actions.ts b/src/app/calendar/actions.ts index 9ba3835..1707099 100644 --- a/src/app/calendar/actions.ts +++ b/src/app/calendar/actions.ts @@ -1,3 +1,4 @@ +import { Database } from "@/types/database.types"; import { Session, SupabaseClient } from "@supabase/supabase-js"; export async function createCalendarEvent( @@ -70,3 +71,44 @@ export async function createMeetingLog({ return error ? { status: false, error } : { status: true, error: null }; } + +export function getMeetingLog(client: SupabaseClient, projectId: number) { + return client + .from("meeting_log") + .select( + ` + id, + meet_date, + start_time, + end_time, + note, + user_id, + project_id, + created_at + ` + ) + .eq("project_id", projectId); +} + +export function getFreeDate(client: SupabaseClient, projectId: number) { + return client.from("project_meeting_time").select("*").eq("project_id", projectId); +} + +export async function specifyFreeDate({ + client, + meet_date, + projectId, +}: { + client: SupabaseClient; + meet_date: string; + projectId: number; +}) { + const { error } = await client.from("project_meeting_time").insert([ + { + project_id: projectId, + meet_date: meet_date, // Format date as YYYY-MM-DD + }, + ]); + + return error ? { status: false, error } : { status: true, error: null }; +} diff --git a/src/app/calendar/manage/FreeTimeDialog.tsx b/src/app/calendar/manage/FreeTimeDialog.tsx new file mode 100644 index 0000000..625615c --- /dev/null +++ b/src/app/calendar/manage/FreeTimeDialog.tsx @@ -0,0 +1,196 @@ +import React, { useState } from "react"; +import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Calendar, DateValue } from "@nextui-org/calendar"; +import { specifyFreeDate, getFreeDate } from "../actions"; +import toast from "react-hot-toast"; +import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date"; +import { Label } from "@/components/ui/label"; +import { useQuery } from "@supabase-cache-helpers/postgrest-react-query"; +import { LegacyLoader } from "@/components/loading/LegacyLoader"; +import { + AlertDialog, + AlertDialogContent, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogCancel, + AlertDialogAction, +} from "@/components/ui/alert-dialog"; + +interface DialogProps { + children?: React.ReactNode; + open?: boolean; + defaultOpen?: boolean; + // eslint-disable-next-line no-unused-vars + onOpenChange?(open: boolean): void; + modal?: boolean; + projectId: number; +} + +export default function FreeTimeDialog(props: DialogProps) { + const supabase = createSupabaseClient(); + const timezone = getLocalTimeZone(); + const [selectedDate, setSelectedDate] = useState(undefined); + const [isSubmitting, setIsSubmitting] = useState(false); + const [deleteDateId, setDeleteDateId] = useState(null); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + + const { + data: freeDate, + error: freeDateError, + isLoading: isLoadingFreeDate, + refetch: refetchFreeDate, + } = useQuery(getFreeDate(supabase, props.projectId), { enabled: !!props.projectId }); + + const handleSpecifyFreeDate = async () => { + if (!selectedDate) { + toast.error("Please select a date."); + return; + } + + setIsSubmitting(true); + + try { + const formattedDate = selectedDate.toString().split("T")[0]; + const { status, error } = await specifyFreeDate({ + client: supabase, + meet_date: formattedDate, + projectId: props.projectId, + }); + + if (!status || error) { + toast.error("Failed to save the free date. Please try again."); + return; + } + refetchFreeDate(); + toast.success("Free time specified successfully!"); + props.onOpenChange?.(false); + } catch (error) { + toast.error("There was an error specifying the free date. Please try again."); + } finally { + setIsSubmitting(false); + } + }; + + const handleDeleteFreeDate = async () => { + if (deleteDateId === null) return; + + setIsSubmitting(true); + + try { + const { error } = await supabase.from("project_meeting_time").delete().eq("id", deleteDateId); + + if (error) { + toast.error("Failed to delete the free date. Please try again."); + return; + } + + refetchFreeDate(); + toast.success("Free date deleted successfully!"); + } catch (error) { + toast.error("There was an error deleting the free date. Please try again."); + } finally { + setIsSubmitting(false); + setIsDeleteDialogOpen(false); + } + }; + + const meetingLogRanges = (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 isDateUnavailable = (date: DateValue) => + disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0); + + return ( + + + + Specify Your Free Time + Select a date when you are available for a meeting with the investor. + + + {freeDateError ? ( +
Error Loading data
+ ) : isLoadingFreeDate ? ( + + ) : ( +
+
+ + +
+
+ +
+ {freeDate && freeDate.length > 0 ? ( + freeDate.map((date) => ( +
{ + setDeleteDateId(date.id); + setIsDeleteDialogOpen(true); + }} + > + {date.meet_date || "No date specified"} +
+ )) + ) : ( +
No free dates specified
+ )} +
+
+
+ )} + + + + + + + +
+ + + + + Are you absolutely sure? + + This action cannot be undone. This will permanently delete the free date. + + + + setIsDeleteDialogOpen(false)}>Cancel + Continue + + + +
+ ); +} diff --git a/src/app/calendar/manage/ManageMeetDialog.tsx b/src/app/calendar/manage/ManageMeetDialog.tsx index a09807a..0da9361 100644 --- a/src/app/calendar/manage/ManageMeetDialog.tsx +++ b/src/app/calendar/manage/ManageMeetDialog.tsx @@ -15,7 +15,7 @@ import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, Table import { Clock } from "lucide-react"; import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; -import getMeetingLog from "./actions"; +import { getMeetingLog } from "../actions"; import { useQuery } from "@supabase-cache-helpers/postgrest-react-query"; import { LegacyLoader } from "@/components/loading/LegacyLoader"; diff --git a/src/app/calendar/manage/ProjectCardSection.tsx b/src/app/calendar/manage/ProjectCardSection.tsx index 75f238e..38b7f7f 100644 --- a/src/app/calendar/manage/ProjectCardSection.tsx +++ b/src/app/calendar/manage/ProjectCardSection.tsx @@ -2,10 +2,11 @@ import { useState } from "react"; import { Separator } from "@/components/ui/separator"; -import { Card, CardContent, CardDescription, CardTitle, CardHeader, CardFooter } from "@/components/ui/card"; +import { Card, CardContent, CardDescription, CardTitle, CardHeader } from "@/components/ui/card"; import { Tooltip, TooltipProvider, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { ManageMeetDialog } from "./ManageMeetDialog"; import { Button } from "@/components/ui/button"; +import FreeTimeDialog from "./FreeTimeDialog"; type ProjectCardSectionProps = { id: number; @@ -22,6 +23,7 @@ type ProjectCardCalendarManageSectionProps = { }; export default function ProjectCardCalendarManageSection({ projectData }: ProjectCardCalendarManageSectionProps) { const [showMeetModal, setShowMeetModal] = useState(false); + const [showFreeTimeModal, setShowFreeTimeModal] = useState(false); const [currentProjectId, setCurrentProjectId] = useState(undefined); return ( @@ -41,7 +43,7 @@ export default function ProjectCardCalendarManageSection({ projectData }: Projec - + + - + {/* */} )) ) : ( -
No data
+
No project data
)} + ); } diff --git a/src/app/calendar/manage/actions.ts b/src/app/calendar/manage/actions.ts deleted file mode 100644 index 37121ab..0000000 --- a/src/app/calendar/manage/actions.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Database } from "@/types/database.types"; -import { SupabaseClient } from "@supabase/supabase-js"; - -export default function getMeetingLog(client: SupabaseClient, projectId: number) { - return client - .from("meeting_log") - .select( - ` - id, - meet_date, - start_time, - end_time, - note, - user_id, - project_id, - created_at - ` - ) - .eq("project_id", projectId); -} diff --git a/src/types/database.types.ts b/src/types/database.types.ts index acb4fe9..74da55c 100644 Binary files a/src/types/database.types.ts and b/src/types/database.types.ts differ