mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-18 13:34:06 +01:00
feat: add page to specify freetime
This commit is contained in:
parent
af351aa481
commit
11ecd3e16f
@ -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 };
|
||||
}
|
||||
|
||||
196
src/app/calendar/manage/FreeTimeDialog.tsx
Normal file
196
src/app/calendar/manage/FreeTimeDialog.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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.
Loading…
Reference in New Issue
Block a user