Skip to content

Commit

Permalink
feat(dashboard): improve application list
Browse files Browse the repository at this point in the history
  • Loading branch information
Matej Tarca committed Nov 7, 2023
1 parent 6f0c54a commit ee58fa2
Show file tree
Hide file tree
Showing 9 changed files with 357 additions and 89 deletions.
2 changes: 1 addition & 1 deletion e2e/applicationForm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ test.describe("application form", () => {
await expect(page.getByText("Test Testovic")).toBeVisible();
await expect(page.getByText("submitted")).toBeVisible();

await page.getByRole("link", { name: "Application detail 1" }).click();
await page.getByRole("link", { name: "Details" }).click();

await expect(
page.getByRole("heading", {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import requireOrganizerApp from "@/services/helpers/requireOrganizerApp";
import { redirect } from "next/navigation";
import { Metadata } from "next";
import ApplicationDetailCard from "@/scenes/Dashboard/scenes/ApplicationDetail/ApplicationDetailCard";
import getApplicationDetail from "@/server/getters/dashboard/applicationDetail";

export const metadata: Metadata = {
title: "Application detail",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React from "react";
import { Button } from "@/components/ui/button";
import { Text } from "@/components/ui/text";
import { Stack } from "@/components/ui/stack";
import Link from "next/link";
import {
Card,
Expand All @@ -10,7 +8,6 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import getApplicationDetail from "@/server/getters/dashboard/applicationDetail";
import ApplicationDetail from "@/scenes/Dashboard/scenes/ApplicationDetail/components/ApplicationDetail";

export type Props = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,213 @@
import React from "react";
import { InformationCircleIcon } from "@heroicons/react/24/outline";
"use client";

import React, { useEffect, useMemo } from "react";
import {
ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
VisibilityState,
} from "@tanstack/react-table";
import { ApplicationData } from "@/server/getters/dashboard/applicationList";
import { ApplicationFormValuesObject } from "@/server/services/helpers/applications/createFormValuesObject";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { ChevronDown } from "lucide-react";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import Link from "next/link";
import getApplicationsList from "@/server/getters/dashboard/applicationList";

const ActionsCell = ({
applicationValues,
}: {
applicationValues: ApplicationFormValuesObject;
}) => {
return (
<Link
href={`applications/${applicationValues.id}/detail`}
className="text-hkOrange"
>
Details
</Link>
);
};

type ApplicationsTableProps = {
hackathonId: number;
applicationValues: ApplicationFormValuesObject[];
applications: ApplicationData[];
};
const ApplicationsTable = async ({ hackathonId }: ApplicationsTableProps) => {
const { applications } = await getApplicationsList(hackathonId);
const ApplicationsTable = ({
hackathonId,
applicationValues,
applications,
}: ApplicationsTableProps) => {
const columns: ColumnDef<ApplicationFormValuesObject>[] = useMemo(
() => [
...Object.keys(applications[0].properties).map((key) => ({
header: key,
accessorKey: key,
})),
{
id: "Actions",
cell: ({ row }) => <ActionsCell applicationValues={row.original} />,
},
],
[applications]
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const table = useReactTable({
data: applicationValues,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
state: {
columnVisibility,
},
onColumnVisibilityChange: setColumnVisibility,
});

const saveColumnVisibility = (columnName: string, visibility: boolean) => {
const oldColumnVisibility =
localStorage.getItem(`hackathon-${hackathonId}-column-visibility`) ??
"{}";
const oldColumnVisibilityObject = JSON.parse(oldColumnVisibility);
const newColumnVisibilityObject = {
...oldColumnVisibilityObject,
[columnName]: visibility,
};
localStorage.setItem(
`hackathon-${hackathonId}-column-visibility`,
JSON.stringify(newColumnVisibilityObject)
);
};

useEffect(() => {
const savedColumnVisibility = localStorage.getItem(
`hackathon-${hackathonId}-column-visibility`
);
if (savedColumnVisibility) {
setColumnVisibility(JSON.parse(savedColumnVisibility));
}
}, [hackathonId]);

return (
<div className="relative overflow-x-auto mt-5">
<table className="w-full text-sm text-left text-gray-500 ">
<thead className="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th scope="col" className="px-6 py-3">
Full name
</th>
<th scope="col" className="px-6 py-3">
School
</th>
<th scope="col" className="px-6 py-3">
Status
</th>
<th scope="col" className="px-6 py-3"></th>
</tr>
</thead>
<tbody>
{applications.map((application, index) => (
<tr className="bg-white border-b" key={application.id}>
<th
scope="row"
className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap"
>
{application.values["Full name"]}
</th>
<td className="px-6 py-4">{application.values["School"]}</td>
<td className="px-6 py-4">{application.status}</td>
<td className="px-6 py-4">
<Link
href={`/dashboard/${hackathonId}/applications/${application.id}/detail`}
aria-label={`Application detail ${index + 1}`}
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Columns
<ChevronDown className="ml-2" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide() && column.id !== "Actions")
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize cursor-pointer"
checked={column.getIsVisible()}
onCheckedChange={(value) => {
column.toggleVisibility(value);
saveColumnVisibility(column.id, value);
}}
onSelect={(event) => event.preventDefault()}
>
<InformationCircleIcon className="w-5 h-5 text-hkOrange inline" />
</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
<ScrollArea className="max-h-[400px] w-[96vw] md:max-w-[61vw]">
<div className="rounded-md border">
<Table className="w-[95vw] md:w-max md:min-w-[60vw]">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className="p-2">
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="small"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="small"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</>
);
};

Expand Down
36 changes: 28 additions & 8 deletions src/scenes/Dashboard/scenes/ApplicationsList/ApplicationsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,40 @@ import React from "react";
import ApplicationsTable from "@/scenes/Dashboard/scenes/ApplicationFormEditor/components/ApplicationsTable";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import getApplicationsList from "@/server/getters/dashboard/applicationList";
import { Stack } from "@/components/ui/stack";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";

type ApplicationsListProps = {
hackathonId: number;
};
const ApplicationsList = async ({ hackathonId }: ApplicationsListProps) => {
const { applications } = await getApplicationsList(hackathonId);

return (
<>
<Button asChild>
<Link href={`/dashboard/${hackathonId}/applications/review`}>
Review applications
</Link>
</Button>
<ApplicationsTable hackathonId={hackathonId} />
</>
<Card className="w-fit m-auto">
<CardHeader>
<CardTitle>Applications</CardTitle>
</CardHeader>
<CardContent>
<Stack direction="column">
<Button asChild>
<Link href={`/dashboard/${hackathonId}/applications/review`}>
Review applications
</Link>
</Button>
<Stack direction="column">
<ApplicationsTable
hackathonId={hackathonId}
applicationValues={applications.map(
(application) => application.properties
)}
applications={applications}
/>
</Stack>
</Stack>
</CardContent>
</Card>
);
};

Expand Down
Loading

0 comments on commit ee58fa2

Please sign in to comment.