Skip to content

Commit 1f2b19c

Browse files
authored
Merge pull request #18 from nextgen-ui-dev/feature/projects
Feature/projects
2 parents 6fa8cf2 + 373a515 commit 1f2b19c

File tree

7 files changed

+341
-7
lines changed

7 files changed

+341
-7
lines changed

src/layouts/DashboardLayout.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import {
1111
} from "~/components/ui/popover";
1212
import { api } from "~/utils/api";
1313
import { LoadingHero } from "./LoadingHero";
14+
import { usePathname } from "next/navigation";
15+
import { cn } from "~/lib/utils";
16+
1417

1518
export const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({
1619
children,
@@ -22,6 +25,8 @@ export const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({
2225
const { data, isLoading: workspacesLoading } =
2326
api.workspace.getWorkspaceSessions.useQuery();
2427

28+
const pathname = usePathname();
29+
2530
return isLoading || workspaceLoading ? (
2631
<LoadingHero />
2732
) : (
@@ -129,22 +134,33 @@ export const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({
129134
</PopoverContent>
130135
</Popover>
131136
<div className="my-2 max-h-full w-full overflow-y-auto">
132-
<ul className="text-sm">
137+
<ul className="text-sm space-y-2 pt-4">
133138
<li>
134139
<Button
140+
asChild
135141
variant="ghost"
136142
size="sm"
137-
className="flex h-auto w-full flex-row items-center justify-start gap-2 p-2 hover:bg-primary"
143+
className={cn("flex h-auto w-full flex-row items-center justify-start gap-2 p-2 hover:bg-primary", pathname.includes('/projects') && "bg-primary")}
138144
>
139-
<Briefcase size={16} />
140-
<p className="font-semibold">Projects</p>
145+
<Link
146+
href={
147+
"/" +
148+
workspace?.providerId +
149+
":" +
150+
workspace?.providerWorkspaceId +
151+
"/projects"
152+
}
153+
>
154+
<Briefcase size={16} />
155+
<p className="font-semibold">Projects</p>
156+
</Link>
141157
</Button>
142158
</li>
143159
<li>
144160
<Button
145161
variant="ghost"
146162
size="sm"
147-
className="flex h-auto w-full flex-row items-center justify-start gap-2 p-2 hover:bg-primary"
163+
className={cn("flex h-auto w-full flex-row items-center justify-start gap-2 p-2 hover:bg-primary", pathname.includes('/issues') && "bg-primary")}
148164
>
149165
<Ticket size={16} />
150166
<p className="font-semibold">Issues</p>
@@ -155,7 +171,7 @@ export const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({
155171
asChild
156172
variant="ghost"
157173
size="sm"
158-
className="flex h-auto w-full flex-row items-center justify-start gap-2 p-2 hover:bg-primary"
174+
className={cn("flex h-auto w-full flex-row items-center justify-start gap-2 p-2 hover:bg-primary", pathname.includes('/documents') && "bg-primary")}
159175
>
160176
<Link
161177
href={
@@ -176,7 +192,7 @@ export const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({
176192
asChild
177193
variant={"ghost"}
178194
size={"sm"}
179-
className="flex h-auto w-full flex-row items-center justify-start gap-2 p-2 hover:bg-primary"
195+
className={cn("flex h-auto w-full flex-row items-center justify-start gap-2 p-2 hover:bg-primary", pathname.includes('/generate') && "bg-primary")}
180196
>
181197
<Link
182198
href={
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { useRouter } from "next/router";
2+
import axios from "axios";
3+
import Head from "next/head";
4+
import { withAuth } from "~/components/withAuth";
5+
import { DashboardLayout } from "~/layouts/DashboardLayout";
6+
import { useEffect } from "react";
7+
import { api } from "~/utils/api";
8+
import { LoadingHero } from "~/layouts/LoadingHero";
9+
10+
export const ProjectDetailPage = withAuth(() => {
11+
const router = useRouter();
12+
const projectId = router.asPath
13+
.replace("/", "")
14+
.replace("/projects", "")
15+
.split("/")[1]!;
16+
17+
const { data, isLoading } = api.user.getSessionInfo.useQuery();
18+
const {
19+
data: documentData,
20+
isLoading: documentLoading,
21+
error,
22+
} = api.workspace.linear.getProjectDetail.useQuery({ projectId });
23+
24+
useEffect(() => {
25+
const [workspaceId, documentId] = router.asPath
26+
.replace("/", "")
27+
.replace("/projects", "")
28+
.split("/");
29+
if (!isLoading && data?.session?.workspace_id !== workspaceId)
30+
void (async function (workspaceId: string, sessionId: string) {
31+
await axios.post("/api/auth/update-session", {
32+
sessionId,
33+
workspaceId,
34+
});
35+
await router.push("/" + workspaceId + "/projects/" + documentId);
36+
router.reload();
37+
})(workspaceId!, data!.session!.id);
38+
}, [data, router, isLoading]);
39+
40+
return (
41+
<>
42+
<Head>
43+
<title>
44+
{!documentLoading && error === null
45+
? documentData?.name + " | "
46+
: ""}
47+
Chiral
48+
</title>
49+
<meta name="description" content="Automate your product backlogs" />
50+
<link rel="icon" href="/favicon.ico" />
51+
</Head>
52+
<DashboardLayout>
53+
{documentLoading ? (
54+
<LoadingHero />
55+
) : error !== null ? (
56+
<main className="flex min-h-screen w-full flex-col p-8">
57+
<h1 className="text-4xl font-bold">{error.message}</h1>
58+
</main>
59+
) : (
60+
<main className="flex min-h-screen w-full flex-row">
61+
62+
</main>
63+
)}
64+
</DashboardLayout>
65+
</>
66+
);
67+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useRouter } from "next/router";
2+
import axios from "axios";
3+
import Head from "next/head";
4+
import { withAuth } from "~/components/withAuth";
5+
import { DashboardLayout } from "~/layouts/DashboardLayout";
6+
import { useEffect } from "react";
7+
import { api } from "~/utils/api";
8+
import { LoadingHero } from "~/layouts/LoadingHero";
9+
import { DocumentsDataTable } from "./components/DocumentsDataTable";
10+
import { ProjectsDataTable } from "./components/ProjectsDataTable";
11+
12+
export const ProjectsPage = withAuth(() => {
13+
const router = useRouter();
14+
15+
const { data, isLoading } = api.user.getSessionInfo.useQuery();
16+
const { data: documentsData, isLoading: documentsLoading } =
17+
api.workspace.linear.getProjects.useQuery();
18+
19+
useEffect(() => {
20+
if (
21+
!isLoading &&
22+
data?.session?.workspace_id !==
23+
router.asPath.replace("/", "").replace("/projects", "")
24+
)
25+
void (async function (workspaceId: string, sessionId: string) {
26+
await axios.post("/api/auth/update-session", {
27+
sessionId,
28+
workspaceId,
29+
});
30+
await router.push("/" + workspaceId + "/projects");
31+
router.reload();
32+
})(
33+
router.asPath.replace("/", "").replace("/projects", ""),
34+
data!.session!.id,
35+
);
36+
}, [data, router, isLoading]);
37+
38+
return (
39+
<>
40+
<Head>
41+
<title>Chiral</title>
42+
<meta name="description" content="Automate your product backlogs" />
43+
<link rel="icon" href="/favicon.ico" />
44+
</Head>
45+
<DashboardLayout>
46+
{documentsLoading ? (
47+
<LoadingHero />
48+
) : (
49+
<main className="flex min-h-screen w-full flex-col p-8">
50+
<h1 className="text-4xl font-bold">Projects</h1>
51+
<ProjectsDataTable projects={documentsData?.projects ?? []} />
52+
</main>
53+
)}
54+
</DashboardLayout>
55+
</>
56+
);
57+
});
58+
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import type { Project, User } from "@linear/sdk";
2+
import {
3+
type ColumnDef,
4+
type SortingState,
5+
useReactTable,
6+
getCoreRowModel,
7+
getSortedRowModel,
8+
flexRender,
9+
} from "@tanstack/react-table";
10+
import { ArrowUpDown } from "lucide-react";
11+
import { useRouter } from "next/router";
12+
import { useState } from "react";
13+
import { Button } from "~/components/ui/button";
14+
import {
15+
Table,
16+
TableBody,
17+
TableCell,
18+
TableHead,
19+
TableHeader,
20+
TableRow,
21+
} from "~/components/ui/table";
22+
23+
type ProjectData = Pick<Project, "id"| "name"> & {
24+
creator: User | undefined;
25+
};
26+
27+
const columns: ColumnDef<ProjectData>[] = [
28+
{
29+
accessorKey: "name",
30+
header: ({ column }) => {
31+
return (
32+
<Button
33+
variant="ghost"
34+
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
35+
className="flex flex-row items-center gap-2 p-0 font-bold hover:bg-inherit"
36+
>
37+
Project
38+
<ArrowUpDown className="h-4 w-4" />
39+
</Button>
40+
);
41+
},
42+
},
43+
{
44+
accessorKey: "creator",
45+
header: ({ column }) => {
46+
return (
47+
<Button
48+
variant="ghost"
49+
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
50+
className="flex flex-row items-center gap-2 p-0 font-bold hover:bg-inherit"
51+
>
52+
Title
53+
<ArrowUpDown className="h-4 w-4" />
54+
</Button>
55+
);
56+
},
57+
},
58+
];
59+
60+
export const ProjectsDataTable: React.FC<{ projects: ProjectData[] }> = ({
61+
projects,
62+
}) => {
63+
const [sorting, setSorting] = useState<SortingState>([]);
64+
65+
const table = useReactTable({
66+
data: projects,
67+
columns,
68+
getCoreRowModel: getCoreRowModel(),
69+
onSortingChange: setSorting,
70+
getSortedRowModel: getSortedRowModel(),
71+
state: {
72+
sorting,
73+
},
74+
});
75+
76+
const router = useRouter();
77+
78+
return (
79+
<div className="my-8 rounded-md border border-primary-dark">
80+
<Table>
81+
<TableHeader>
82+
{table.getHeaderGroups().map((headerGroup) => (
83+
<TableRow
84+
key={headerGroup.id}
85+
className="bg-primary-dark hover:bg-primary-dark"
86+
>
87+
{headerGroup.headers.map((header) => (
88+
<TableHead key={header.id}>
89+
{header.isPlaceholder
90+
? null
91+
: flexRender(
92+
header.column.columnDef.header,
93+
header.getContext(),
94+
)}
95+
</TableHead>
96+
))}
97+
</TableRow>
98+
))}
99+
</TableHeader>
100+
<TableBody>
101+
{table.getRowModel().rows.length > 0 &&
102+
table.getRowModel().rows.map((row) => (
103+
<TableRow
104+
key={row.id}
105+
onClick={() =>
106+
void router.push(router.asPath + "/" + row.original.id)
107+
}
108+
>
109+
{row.getVisibleCells().map((cell) => {
110+
if (cell.column.id === "project") {
111+
return (
112+
<TableCell key={cell.id}>
113+
{cell.row.original.name}
114+
</TableCell>
115+
);
116+
} else if (cell.column.id === "creator") {
117+
return (
118+
<TableCell key={cell.id}>
119+
{cell.row.original.creator?.displayName}
120+
</TableCell>
121+
);
122+
}
123+
return (
124+
<TableCell key={cell.id}>
125+
{flexRender(
126+
cell.column.columnDef.cell,
127+
cell.getContext(),
128+
)}
129+
</TableCell>
130+
);
131+
})}
132+
</TableRow>
133+
))}
134+
</TableBody>
135+
</Table>
136+
</div>
137+
);
138+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { NextPage } from "next";
2+
import { ProjectDetailPage } from "~/modules/workspace/ProjectDetailPage";
3+
4+
const ProjectDetail: NextPage = () => <ProjectDetailPage />;
5+
6+
export default ProjectDetail;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { NextPage } from "next";
2+
import { ProjectsPage } from "~/modules/workspace/ProjectsPage";
3+
4+
const Projects: NextPage = () => <ProjectsPage />;
5+
6+
export default Projects;

0 commit comments

Comments
 (0)