feat: add page to specify freetime

This commit is contained in:
Sosokker 2024-11-15 16:15:12 +07:00
parent af351aa481
commit 11ecd3e16f
6 changed files with 254 additions and 25 deletions

View File

@ -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<Database>, 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<Database>, projectId: number) {
return client.from("project_meeting_time").select("*").eq("project_id", projectId);
}
export async function specifyFreeDate({
client,
meet_date,
projectId,
}: {
client: SupabaseClient<Database>;
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 };
}

View File

@ -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<CalendarDate | undefined>(undefined);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [deleteDateId, setDeleteDateId] = useState<number | null>(null);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState<boolean>(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 (
<Dialog {...props}>
<DialogContent className="sm:max-w-md overflow-y-auto h-[80%]">
<DialogHeader>
<DialogTitle className="flex gap-2 items-center">Specify Your Free Time</DialogTitle>
<DialogDescription>Select a date when you are available for a meeting with the investor.</DialogDescription>
</DialogHeader>
{freeDateError ? (
<div>Error Loading data</div>
) : isLoadingFreeDate ? (
<LegacyLoader />
) : (
<div className="flex flex-col space-y-4 w-[90%] mt-4">
<div className="flex flex-col space-y-2">
<Label>Date</Label>
<Calendar
value={selectedDate}
onChange={setSelectedDate}
minValue={today(timezone)}
isDateUnavailable={isDateUnavailable}
/>
</div>
<div className="flex flex-col space-y-2 mt-4">
<Label>Current Free Dates</Label>
<div className="p-2 border rounded-md space-y-2">
{freeDate && freeDate.length > 0 ? (
freeDate.map((date) => (
<div
key={date.id}
className="bg-gray-100 p-2 rounded cursor-pointer hover:bg-red-400"
onClick={() => {
setDeleteDateId(date.id);
setIsDeleteDialogOpen(true);
}}
>
{date.meet_date || "No date specified"}
</div>
))
) : (
<div>No free dates specified</div>
)}
</div>
</div>
</div>
)}
<DialogFooter className="sm:justify-start mt-4">
<Button onClick={handleSpecifyFreeDate} disabled={isSubmitting}>
{isSubmitting ? "Saving..." : "Save Free Date"}
</Button>
<DialogClose asChild>
<Button type="button" variant="secondary">
Close
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the free date.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setIsDeleteDialogOpen(false)}>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDeleteFreeDate}>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Dialog>
);
}

View File

@ -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";

View File

@ -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<boolean>(false);
const [showFreeTimeModal, setShowFreeTimeModal] = useState<boolean>(false);
const [currentProjectId, setCurrentProjectId] = useState<number | undefined>(undefined);
return (
@ -41,7 +43,7 @@ export default function ProjectCardCalendarManageSection({ projectData }: Projec
</TooltipProvider>
</CardHeader>
<Separator className="mb-3" />
<CardContent>
<CardContent className="flex gap-3">
<Button
onClick={() => {
setCurrentProjectId(project.id);
@ -50,14 +52,23 @@ export default function ProjectCardCalendarManageSection({ projectData }: Projec
>
Meeting List
</Button>
<Button
onClick={() => {
setCurrentProjectId(project.id);
setTimeout(() => setShowFreeTimeModal(true), 0);
}}
>
Specify Free Time
</Button>
</CardContent>
<CardFooter></CardFooter>
{/* <CardFooter></CardFooter> */}
</Card>
))
) : (
<div>No data</div>
<div>No project data</div>
)}
<ManageMeetDialog open={showMeetModal} onOpenChange={setShowMeetModal} projectId={currentProjectId!} />
<FreeTimeDialog open={showFreeTimeModal} onOpenChange={setShowFreeTimeModal} projectId={currentProjectId!} />
</div>
);
}

View File

@ -1,20 +0,0 @@
import { Database } from "@/types/database.types";
import { SupabaseClient } from "@supabase/supabase-js";
export default function getMeetingLog(client: SupabaseClient<Database>, 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);
}

Binary file not shown.