diff --git a/apps/nextjs/next.config.js b/apps/nextjs/next.config.js index f095638..be878fc 100644 --- a/apps/nextjs/next.config.js +++ b/apps/nextjs/next.config.js @@ -8,7 +8,7 @@ createJiti(fileURLToPath(import.meta.url))("./src/env"); const config = { reactStrictMode: true, images: { - domains: ["lh3.googleusercontent.com"] + domains: ["lh3.googleusercontent.com"], }, /** Enables hot reloading for local packages without a build step */ diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/_components/LastItem.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/_components/LastItem.tsx index 038d00c..46d05db 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/_components/LastItem.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/_components/LastItem.tsx @@ -1,23 +1,20 @@ -'use client' -import { usePathname } from "next/navigation"; +"use client"; + import { memo } from "react"; -import { captilize } from "~/lib/utils"; +import { usePathname } from "next/navigation"; +import { captilize } from "~/lib/utils"; -function GetLastBreadCrumb(props: { - id: string -}) { - const { id } = props - const pathname = usePathname() - let ending = pathname[-1] +function GetLastBreadCrumb(props: { id: string }) { + const { id } = props; + const pathname = usePathname(); + let ending = pathname[-1]; if (!ending || ending === id) { - ending = "Overview Page" + ending = "Overview Page"; } - return ( -
{captilize(ending)}
- ) + return
{captilize(ending)}
; } -export default memo(GetLastBreadCrumb) +export default memo(GetLastBreadCrumb); diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/layout.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/layout.tsx index 1b1dfc5..97dfcb7 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/layout.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/layout.tsx @@ -1,56 +1,52 @@ -import { Input } from '@amaxa/ui/input' -import { Tooltip, TooltipTrigger, TooltipContent } from '@amaxa/ui/tooltip' -import GetLastBreadCrumb from "./_components/LastItem" +import React, { cache } from "react"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { Home, Package2, Search, Settings, Workflow } from "lucide-react"; + +import { db } from "@amaxa/db/client"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, - BreadcrumbSeparator -} from '@amaxa/ui/breadcrumb' -import { - Home, - Package2, - Search, - Settings, - Workflow -} from "lucide-react" -import Link from 'next/link' -import React, { cache } from 'react' -import { UserMenu } from '~/components/UserMenu' -import { db } from '@amaxa/db/client' -import { notFound } from 'next/navigation' -import ComingSoon from '~/components/ComingSoon' + BreadcrumbSeparator, +} from "@amaxa/ui/breadcrumb"; +import { Input } from "@amaxa/ui/input"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@amaxa/ui/tooltip"; + +import ComingSoon from "~/components/ComingSoon"; +import { UserMenu } from "~/components/UserMenu"; +import GetLastBreadCrumb from "./_components/LastItem"; const getProjectInfo = cache(async () => { const data = await db.query.Projects.findFirst({ columns: { name: true, - id: true - } - }) - return data -}) + id: true, + }, + }); + return data; +}); export default async function Layout({ children, - params + params, }: { - children: React.ReactNode, + children: React.ReactNode; params: { id: string; - } + }; }) { - const { id } = params - const data = await getProjectInfo() + const { id } = params; + const data = await getProjectInfo(); if (!data) { - return notFound() + return notFound(); } return ( -
+
-
@@ -133,12 +127,14 @@ export default async function Layout({ - + + + -
+
-
{children}
- - ) + ); } - diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/page.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/page.tsx index e7d4972..04357fc 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/page.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/page.tsx @@ -1,9 +1,6 @@ -"use client" -import type { Metadata, ResolvingMetadata } from "next"; -import { TrendingUp, TrendingDown } from "lucide-react" - -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@amaxa/ui/card"; +"use client"; +import { TrendingUp } from "lucide-react"; import { Area, AreaChart, @@ -14,14 +11,23 @@ import { Pie, PieChart, XAxis, - YAxis -} from "recharts" + YAxis, +} from "recharts"; + +import type { ChartConfig } from "@amaxa/ui/chart"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@amaxa/ui/card"; import { - ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, -} from "@amaxa/ui/chart" +} from "@amaxa/ui/chart"; interface ProjectPageProps { params: { @@ -29,17 +35,14 @@ interface ProjectPageProps { }; } - const chartConfig = { tasksFinished: { label: "Tasks Finished", color: "hsl(var(--primary))", }, -} satisfies ChartConfig - +} satisfies ChartConfig; - -const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042'] +const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042"]; const getTaskData = () => { return [ { month: "January", tasksFinished: 18 }, @@ -48,16 +51,16 @@ const getTaskData = () => { { month: "April", tasksFinished: 7 }, { month: "May", tasksFinished: 20 }, { month: "June", tasksFinished: 21 }, - ] -} + ]; +}; const getPriorityData = () => { return [ { priority: "Low", count: 15 }, { priority: "Medium", count: 30 }, { priority: "High", count: 20 }, - ] -} + ]; +}; const getStatusData = () => { return [ @@ -65,9 +68,8 @@ const getStatusData = () => { { status: "in-progress", count: 15 }, { status: "done", count: 40 }, { status: "unable-to-complete", count: 5 }, - ] -} - + ]; +}; export default function HomePage({ params }: ProjectPageProps) { const { id } = params; @@ -76,11 +78,10 @@ export default function HomePage({ params }: ProjectPageProps) { const statusData = getStatusData(); return (
-
- Tasks Finished Over Time + Tasks Finished Over Time project {id} Number of tasks completed each month @@ -100,7 +101,12 @@ export default function HomePage({ params }: ProjectPageProps) { } /> - + @@ -108,7 +114,8 @@ export default function HomePage({ params }: ProjectPageProps) {
- Trending up by 10% this month + Trending up by 10% this month{" "} +
January - June 2024 @@ -121,9 +128,7 @@ export default function HomePage({ params }: ProjectPageProps) { Task Priority Distribution - - Breakdown of tasks by priority - + Breakdown of tasks by priority @@ -138,24 +143,23 @@ export default function HomePage({ params }: ProjectPageProps) { dataKey="count" > {priorityData.map((entry, index) => ( - + ))} } /> - - Low, Medium, and High priority tasks - + Low, Medium, and High priority tasks Task Status Overview - - Current status of all tasks - + Current status of all tasks diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/permissions-dialog.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/permissions-dialog.tsx index 55952e7..465b633 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/permissions-dialog.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/permissions-dialog.tsx @@ -1,17 +1,22 @@ -import { Button } from '@amaxa/ui/button' -import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '@amaxa/ui/dialog' -import React from 'react' +import React from "react"; + +import { Button } from "@amaxa/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTrigger, +} from "@amaxa/ui/dialog"; export default function PermissionsModal({ projectId, userId, defaultPermissions, }: { - projectId: string - userId: string - defaultPermissions: string[] + projectId: string; + userId: string; + defaultPermissions: string[]; }) { - return ( @@ -25,11 +30,8 @@ export default function PermissionsModal({
- {defaultPermissions.map((permission) => ( - `Permission: ${permission}` - ))} + {defaultPermissions.map((permission) => `Permission: ${permission}`)}
- ) + ); } - diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/role-select.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/role-select.tsx index ac095bb..785158a 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/role-select.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/role-select.tsx @@ -1,9 +1,15 @@ -import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@amaxa/ui/select' -import React from 'react' +import React from "react"; + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@amaxa/ui/select"; export default function RoleSelect() { return ( - - ) + ); } - diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx index e260780..a5d8979 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx @@ -1,34 +1,60 @@ -import { Avatar, AvatarImage, AvatarFallback } from '@amaxa/ui/avatar' -import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '@amaxa/ui/table' -import { Button } from '@amaxa/ui/button' +import React from "react"; +import { MoreVertical } from "lucide-react"; + +import { Avatar, AvatarFallback, AvatarImage } from "@amaxa/ui/avatar"; +import { Button } from "@amaxa/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@amaxa/ui/card"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@amaxa/ui/dropdown-menu"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@amaxa/ui/select"; import { - MoreVertical, -} from "lucide-react" -import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@amaxa/ui/card' -import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@amaxa/ui/dropdown-menu' -import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@amaxa/ui/select' -import React from 'react' -import PermissionsModal from './_components/permissions-dialog' + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@amaxa/ui/table"; + +import PermissionsModal from "./_components/permissions-dialog"; export default function Page({ - params + params, }: { params: { - id: string - } + id: string; + }; }) { - const { id } = params + const { id } = params; return ( -
-
+
+

Permissions

-
+
Project Collaborators - Manage user permissions for this project. + + Manage user permissions for this project. + @@ -52,7 +78,9 @@ export default function Page({
Olivia Davis
-
olivia@example.com
+
+ olivia@example.com +
@@ -69,13 +97,21 @@ export default function Page({ - + - @@ -95,12 +131,13 @@ export default function Page({
Isabella Nguyen
-
isabella@example.com
+
+ isabella@example.com +
- - + @@ -130,7 +171,9 @@ export default function Page({
Sofia Davis
-
sofia@example.com
+
+ sofia@example.com +
@@ -154,8 +197,12 @@ export default function Page({ - @@ -172,6 +219,5 @@ export default function Page({ - ) + ); } - diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_actions.ts b/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_actions.ts index 5074818..e947170 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_actions.ts +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_actions.ts @@ -1,16 +1,19 @@ -"use server" -import { db } from "@amaxa/db/client"; -import { buildConflictUpdateColumns } from "@amaxa/db"; -import { edges, tasks, TaskStatus } from "@amaxa/db/schema"; +"use server"; + import { z } from "zod"; + +import type { TaskStatus } from "@amaxa/db/schema"; import { auth } from "@amaxa/auth"; +import { buildConflictUpdateColumns } from "@amaxa/db"; +import { db } from "@amaxa/db/client"; +import { edges, tasks } from "@amaxa/db/schema"; const schema = z.object({ tasks: z.array( z.object({ id: z.string(), type: z.string().optional(), - parentId: z.string().optional(), + parentId: z.string().optional().default("noid"), position: z.object({ x: z.number(), y: z.number(), @@ -20,15 +23,17 @@ const schema = z.object({ status: z.string(), description: z.string(), assigne: z.object({ - id: z.string(), + id: z.string().default("unassigned"), name: z.string().nullable(), image: z.string().nullable(), - }), // Allow null for the entire assigne object + }), // Allow null for the entire assigne object assigneName: z.string().nullable(), projectId: z.string(), - parent: z.object({ - id: z.string(), - }).nullable(), // Make parent nullable + parent: z + .object({ + id: z.string(), + }) + .nullable(), // Make parent nullable doneBy: z.date(), }), }), @@ -41,33 +46,33 @@ const schema = z.object({ target: z.string(), }), ), -}) +}); type InputProps = z.infer; export async function saveTasks(data: InputProps) { try { - const session = await auth() - if (!session) { + const session = await auth(); + if (!session?.user) { throw new Error("User is not authenticated"); } // Validate input data const validatedData = schema.parse(data); const formattedTasks = validatedData.tasks.map((task) => { - console.log("Task status:", task.data.status) + console.log("Task status:", task.data.status); return { id: task.id, - type: task.type ?? 'task', // Provide a default value if type is undefined + type: task.type, // Provide a default value if type is undefined title: task.data.title, - parentId: task.parentId ?? "noid", // Use null if parentId is undefined + parentId: task.parentId, // Use null if parentId is undefined status: task.data.status as TaskStatus, description: task.data.description, position: task.position, projectId: task.data.projectId, - assigneeId: task.data.assigne?.id ?? 'unassigned', + assigneeId: task.data.assigne.id, doneBy: task.data.doneBy, - } + }; }); // Insert or update tasks await db @@ -86,15 +91,23 @@ export async function saveTasks(data: InputProps) { "label", "priority", "type", - "position" + "position", ]), }); // Insert or update edges - await db.insert(edges).values(validatedData.edges).onConflictDoUpdate({ - target: edges.id, - set: buildConflictUpdateColumns(edges, ["source", "target", "projectId", "id"]), - }); + await db + .insert(edges) + .values(validatedData.edges) + .onConflictDoUpdate({ + target: edges.id, + set: buildConflictUpdateColumns(edges, [ + "source", + "target", + "projectId", + "id", + ]), + }); console.log("Tasks and edges saved successfully"); } catch (error) { diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_components/change-status.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_components/change-status.tsx index 7f1a73b..ba609de 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_components/change-status.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_components/change-status.tsx @@ -1,6 +1,9 @@ "use client"; + import { useState } from "react"; -import { statusValues, TaskStatus } from "@amaxa/db/schema"; + +import type { TaskStatus } from "@amaxa/db/schema"; +import { statusValues } from "@amaxa/db/schema"; import { Select, SelectContent, @@ -9,7 +12,8 @@ import { SelectTrigger, SelectValue, } from "@amaxa/ui/select"; -import useStore from '~/lib/store'; + +import useStore from "~/lib/store"; interface ChangeStatusProps { defaultValue: TaskStatus; @@ -18,7 +22,7 @@ interface ChangeStatusProps { export function ChangeStatus({ defaultValue, id }: ChangeStatusProps) { const [value, setValue] = useState(defaultValue); - const changeNode = useStore(state => state.changeNode); + const changeNode = useStore((state) => state.changeNode); function onSubmit(newValue: TaskStatus) { setValue(newValue); @@ -39,8 +43,6 @@ export function ChangeStatus({ defaultValue, id }: ChangeStatusProps) { ))} - - ); } diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_components/custom-node.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_components/custom-node.tsx index af46d01..faa6a23 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_components/custom-node.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/tasks/_components/custom-node.tsx @@ -1,18 +1,28 @@ -"use client" -import React, { memo } from 'react'; -import { Handle, Position, NodeProps } from '@xyflow/react'; +"use client"; + +import type { NodeProps } from "@xyflow/react"; +import React, { memo } from "react"; +import { Handle, Position } from "@xyflow/react"; + +import type { TaskStatus } from "@amaxa/db/schema"; import { Avatar, AvatarFallback } from "@amaxa/ui/avatar"; -import useStore from '~/lib/store'; +import { Button } from "@amaxa/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@amaxa/ui/card"; +import { Input } from "@amaxa/ui/input"; +import { Label } from "@amaxa/ui/label"; import { Popover, PopoverContent, PopoverTrigger } from "@amaxa/ui/popover"; -import { NodeType } from "~/lib/types/flowcart"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@amaxa/ui/select"; +import { Textarea } from "@amaxa/ui/textarea"; + +import type { NodeType } from "~/lib/types/flowcart"; +import useStore from "~/lib/store"; import { ChangeStatus } from "./change-status"; -import { Button } from '@amaxa/ui/button'; -import { TaskStatus } from '@amaxa/db/schema'; -import { Label } from '@amaxa/ui/label'; -import { Input } from '@amaxa/ui/input'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@amaxa/ui/select'; -import { Textarea } from '@amaxa/ui/textarea'; const getCardClassName = (status: string) => { switch (status) { @@ -27,25 +37,25 @@ const getCardClassName = (status: string) => { const TaskNode = ({ data, isConnectable }: NodeProps) => { const [isEdit, setIsEdit] = React.useState(false); - const authorInitials = data.assigne?.name - ?.split(" ") - .map((n) => n[0]) - .join("") ?? "S"; + const authorInitials = + data.assigne.name + ?.split(" ") + .map((n) => n[0]) + .join("") ?? "S"; - const changeNode = useStore(state => state.changeNode); + const changeNode = useStore((state) => state.changeNode); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); const formData = new FormData(event.currentTarget); const updatedData = { - title: formData.get('title') as string, - description: formData.get('description') as string, - status: formData.get('status') as TaskStatus, + title: formData.get("title") as string, + description: formData.get("description") as string, + status: formData.get("status") as TaskStatus, }; changeNode(data.id, updatedData); setIsEdit(false); }; - if (!isEdit) { return (
@@ -53,9 +63,9 @@ const TaskNode = ({ data, isConnectable }: NodeProps) => { type="target" position={Position.Left} isConnectable={isConnectable} - style={{ left: '-8px', top: '50%', transform: 'translateY(-50%)' }} + style={{ left: "-8px", top: "50%", transform: "translateY(-50%)" }} /> - +

{data.title}

@@ -71,7 +81,9 @@ const TaskNode = ({ data, isConnectable }: NodeProps) => { {authorInitials} -

{data.assigne?.name ?? "System"}

+

+ {data.assigne.name ?? "System"} +

@@ -86,51 +98,63 @@ const TaskNode = ({ data, isConnectable }: NodeProps) => { type="source" position={Position.Right} isConnectable={isConnectable} - style={{ right: '-8px', top: '50%', transform: 'translateY(-50%)' }} + style={{ right: "-8px", top: "50%", transform: "translateY(-50%)" }} /> ); } else { - return - - - Edit Task - - - -
-
-
- - -
-
- -