feat: project detail w/ task rendering

This commit is contained in:
Christopher Arraya 2023-07-30 02:13:35 -04:00
parent d518632820
commit 0284624b1d
6 changed files with 290 additions and 3 deletions

View File

@ -0,0 +1,53 @@
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { Database } from "@/types/supabase";
import prisma from "@/lib/prisma";
import { NextRequest, NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
import { cookies } from "next/headers";
export async function GET(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const supabase = createRouteHandlerClient<Database>({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
throw new Error("Unauthorized");
}
const project = await prisma.project.findUnique({
where: { id: Number(params.id) },
include: { Task: true },
});
if (!project) {
throw new Error("Project not found");
}
const { Task, ...projectData } = project;
const res = {
...projectData,
id: String(project.id),
tasks: Task.map((task) => ({
...task,
id: String(task.id),
projectId: String(task.projectId),
})),
};
const path = req.nextUrl.searchParams.get("path") || "/";
revalidatePath(path);
return NextResponse.json(res, { status: 200 });
} catch (err: any) {
return NextResponse.json({ message: err.message }, { status: 401 });
} finally {
await prisma.$disconnect();
}
}

View File

@ -0,0 +1,58 @@
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { Database } from "@/types/supabase";
import prisma from "@/lib/prisma";
import { NextRequest, NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
import { cookies } from "next/headers";
export async function POST(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const supabase = createRouteHandlerClient<Database>({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
throw new Error("Unauthorized");
}
const { description, status, priority, dueDate, tags } = await req.json();
const newTask = await prisma.task.create({
data: {
description,
status,
priority,
dueDate,
tags,
projectId: Number(params.id),
},
});
await prisma.userProfile_Task.create({
data: {
userProfileId: session.user.id,
taskId: newTask.id,
},
});
const res = {
...newTask,
id: String(newTask.id),
projectId: String(newTask.projectId),
};
const path = req.nextUrl.searchParams.get("path") || "/";
revalidatePath(path);
return NextResponse.json(res, { status: 201 });
} catch (err: any) {
return NextResponse.json({ message: err.message }, { status: 500 });
} finally {
await prisma.$disconnect();
}
}

0
app/api/tasks/route.ts Normal file
View File

View File

@ -0,0 +1,166 @@
"use client";
import { useState, useEffect } from "react";
import { Project, Task } from "@/types/models";
import { Button, buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
const taskSchema = z.object({
description: z.string().min(10, "Description must be at least 10 characters"),
status: z.string().optional(),
priority: z.string().optional(),
dueDate: z.date().optional(),
tags: z.array(z.string()).optional(),
});
export default function Project({ params }: { params: { id: string } }) {
const [project, setProject] = useState<Project | undefined>();
const [loading, setLoading] = useState(true);
const [taskOpen, setTaskOpen] = useState(false);
const form = useForm<z.infer<typeof taskSchema>>({
resolver: zodResolver(taskSchema),
defaultValues: {
description: "",
status: "",
priority: "",
dueDate: undefined,
},
});
useEffect(() => {
if (params.id) {
fetch(`/api/projects/${params.id}`)
.then((res) => {
if (!res.ok) throw new Error("HTTP status " + res.status);
return res.json();
})
.then((data) => {
setProject(data);
setLoading(false);
})
.catch((err) => console.error("Failed to fetch project:", err));
}
}, [params.id]);
async function handleSubmit(values: z.infer<typeof taskSchema>) {
try {
const res = await fetch(`/api/projects/${params.id}/tasks`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
});
if (!res.ok) throw new Error("HTTP status " + res.status);
const newTask = await res.json();
setProject((prevProject) => {
if (!prevProject) return;
const prevTasks = prevProject.tasks || [];
return {
...prevProject,
tasks: [...prevTasks, newTask],
};
});
setTaskOpen(false);
} catch (err) {
console.error("Failed to create task:", err);
}
}
if (loading) return <p>Loading...</p>;
if (!project) return <p>No project found.</p>;
return (
<div>
<h2>{project.title}</h2>
<p>{project.description}</p>
<div>
<h2>{project.title}</h2>
<p>{project.description}</p>
<Dialog open={taskOpen} onOpenChange={setTaskOpen}>
<DialogTrigger className={buttonVariants({ variant: "default" })}>
Add Task
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Task</DialogTitle>
<DialogDescription>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)}>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Task Description</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="status"
render={({ field }) => (
<FormItem>
<FormLabel>Task Status</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="priority"
render={({ field }) => (
<FormItem>
<FormLabel>Task Priority</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
<h3>Tasks</h3>
{project.tasks?.map((task: Task) => (
<div key={task.id}>
<p>{task.description}</p>
</div>
))}
</div>
</div>
);
}

View File

@ -1,5 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { Project } from "@/types/models";
import { Button, buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@ -65,9 +66,7 @@ export default function Projects() {
}, []);
async function handleSubmit(values: z.infer<typeof projectSchema>) {
console.log(values);
try {
console.log();
const res = await fetch("/api/projects", {
method: "POST",
headers: {
@ -178,7 +177,9 @@ export default function Projects() {
<div>
{projects.map((project) => (
<div key={project.id}>
<h2>{project.title}</h2>
<h2>
<Link href={`/projects/${project.id}`}>{project.title}</Link>
</h2>
<p>{project.description}</p>
</div>
))}

View File

@ -4,4 +4,13 @@ export type Project = {
description: string;
github: string;
stack: string[];
tasks: Task[];
};
export type Task = {
id: string;
description: string;
priority: string;
dueDate: string;
tags: string[];
};