Skip to content

Commit

Permalink
DEAR-125: add team member table
Browse files Browse the repository at this point in the history
  • Loading branch information
baurnick committed Jul 21, 2024
1 parent 907b629 commit 413290b
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 43 deletions.
File renamed without changes.
7 changes: 4 additions & 3 deletions app/(main)/team/page.tsx → app/(main)/team/(team)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import Loading from '@components/Loading/Loading';
import Error from '@components/Error/Error';
import useSWRClient from '@hooks/useSWRClient';
import { Team } from '@/types/TeamType';
import { columns } from './components/TeamTable/columns';
import TeamTable from './components/TeamTable/TeamTable';
import { columns } from '../components/TeamTable/columns';
import TeamTable from '../components/TeamTable/TeamTable';

const TeamPage: React.FC = () => {
const { userId } = useAuth();
const { data, isLoading, error } = useSWRClient<Team[]>(`/v1/team/user/${userId}`);

if (isLoading) return <Loading />;
if (error) return <Error errorMessage="It seems there was a problem loading your teams." action="/" showContact />;
if (error)
return <Error errorMessage="It seems there was a problem loading your teams." action="/team" showContact />;

return (
<div>
Expand Down
19 changes: 19 additions & 0 deletions app/(main)/team/(teammember)/[id]/members/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';
import Separtor from '@components/ui/Separator/Separator';

interface TeamMemberLayoutProps {
children: React.ReactNode;
}

const TeamMemberLayout: React.FC<TeamMemberLayoutProps> = ({ children }) => (
<div className="space-y-8 px-16 pb-16">
<div className="space-y-0.5">
<h1>Members</h1>
<p className="text-md font-thin">Manage team members.</p>
</div>
<Separtor className="dark:border-secondaryBG-dark" />
<div>{children}</div>
</div>
);

export default TeamMemberLayout;
38 changes: 38 additions & 0 deletions app/(main)/team/(teammember)/[id]/members/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import * as React from 'react';
import useSWRClient from '@hooks/useSWRClient';
import { TeamWithMembers } from '@/types/TeamType';
import Loading from '@components/Loading/Loading';
import Error from '@components/Error/Error';
import { TeamMemberWithUser } from '@/types/TeamMemberType';
import TeamMemberTable from '../../../components/TeamMembersTable/TeamMemberTable';
import { columns } from '../../../components/TeamMembersTable/columns';

interface TeamMembersPageProps {
params: {
id: string;
};
}

const TeamMembersPage: React.FC<TeamMembersPageProps> = ({ params }) => {
const { id: teamId } = params;
const { data, isLoading, error } = useSWRClient<TeamWithMembers>(`/v1/team-member/${teamId}`);

if (isLoading) return <Loading />;
if (error) return <Error errorMessage="It seems there was a problem loading members." action="/team" showContact />;

return (
<div>
{data ? (
<div>
<TeamMemberTable<TeamMemberWithUser> columns={columns} data={data.members} />
</div>
) : (
<Loading />
)}
</div>
);
};

export default TeamMembersPage;
81 changes: 81 additions & 0 deletions app/(main)/team/components/TeamMembersTable/TeamMemberTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@components/ui/Table/Table';
import {
ColumnDef,
ColumnFiltersState,
flexRender,
getCoreRowModel,
getSortedRowModel,
getFilteredRowModel,
SortingState,
useReactTable,
VisibilityState,
} from '@tanstack/react-table';
import TeamMemberTableToolbar from './TeamMemberTableToolbar';

interface TeamMemberTableProps<TData> {
columns: ColumnDef<TData>[];
data: TData[];
}

const TeamMemberTable = <TData,>({ columns, data }: TeamMemberTableProps<TData>) => {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);

const table = useReactTable<TData>({
data,
columns,
getCoreRowModel: getCoreRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
columnVisibility,
columnFilters,
},
});

return (
<div className="space-y-4">
<TeamMemberTableToolbar table={table} />
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<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}>{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>
</div>
);
};

export default TeamMemberTable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import * as React from 'react';
import Input from '@components/ui/Input/Input';
import { Table } from '@tanstack/react-table';
import TeamMemberTableViewOptions from './TeamMemberTableViewOptions';

interface TeamMemberTableToolbarProps<TData> {
table: Table<TData>;
}

const TeamMemberTableToolbar = <TData,>({ table }: TeamMemberTableToolbarProps<TData>) => (
<div className="flex items-center justify-between">
<div className="max-w-4xl">
<Input
placeholder="Search member"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
className="h-8"
/>
</div>
<TeamMemberTableViewOptions table={table} />
</div>
);

export default TeamMemberTableToolbar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import * as React from 'react';
import { Table } from '@tanstack/react-table';
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuTrigger,
DropdownMenuContent,
} from '@components/ui/DropdownMenu/Dropdown-menu';
import { Button } from '@components/ui/Buttons/Button';
import { Eye } from 'lucide-react';

interface TeamMemberTableViewOptionsProps<TData> {
table: Table<TData>;
}

const TeamMemberTableViewOptions = <TData,>({ table }: TeamMemberTableViewOptionsProps<TData>) => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="ml-auto hidden h-8 lg:flex">
<Eye className="mr-2 h-4 w-4" />
View
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[150px]">
{table
.getAllColumns()
.filter((column) => typeof column.accessorFn !== 'undefined' && column.getCanHide())
.map((column) => (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);

export default TeamMemberTableViewOptions;
55 changes: 55 additions & 0 deletions app/(main)/team/components/TeamMembersTable/columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* eslint-disable import/prefer-default-export */

import { TeamMemberWithUser } from '@/types/TeamMemberType';
import { ColumnDef } from '@tanstack/react-table';
import { Badge } from '@components/ui/Badge/Badge';
import cn from '@/lib/utils';
import DataTableColumnHeader from '@components/ui/Table/DataTableColumnHeader';

export const columns: ColumnDef<TeamMemberWithUser>[] = [
{
id: 'name',
accessorKey: 'user.name',
header: ({ column }) => <DataTableColumnHeader column={column} title="Name" />,
},
{
id: 'username',
accessorKey: 'user.username',
header: ({ column }) => <DataTableColumnHeader column={column} title="Username" />,
cell: ({ row }) => {
const username = row.original.user?.username;
return username || '-';
},
},
{
accessorKey: 'role',
header: 'Role',
cell: ({ row }) => {
const { role } = row.original;
return (
<Badge variant="secondary" className={cn('px-2 font-medium', { 'bg-primaryBlue-light': role === 'ADMIN' })}>
{role}
</Badge>
);
},
},
{
id: 'status',
accessorKey: 'active',
header: ({ column }) => <DataTableColumnHeader column={column} title="Status" />,
cell: ({ row }) => {
const { active } = row.original;
return (
<Badge
variant="secondary"
className={cn('px-2 font-medium', {
'bg-primaryGreen-light': active === true,
'bg-primaryRed-light': active === false,
})}
>
{active ? 'Active' : 'Inactive'}
</Badge>
);
},
},
];
4 changes: 2 additions & 2 deletions app/(main)/team/components/TeamTable/TeamTableToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import * as React from 'react';
import Input from '@components/ui/Input/Input';
import { Table } from '@tanstack/react-table';
import * as React from 'react';
import TeamTableViewOptions from './TeamTableViewOptions';

interface TeamTableToolbarProps<TData> {
Expand All @@ -10,7 +10,7 @@ interface TeamTableToolbarProps<TData> {

const TeamTableToolbar = <TData,>({ table }: TeamTableToolbarProps<TData>) => (
<div className="flex items-center justify-between">
<div className="max-w-2xl">
<div className="max-w-4xl">
<Input
placeholder="Search name"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
Expand Down
22 changes: 13 additions & 9 deletions app/(main)/team/components/TeamTable/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import {
DropdownMenuContent,
DropdownMenuItem,
} from '@components/ui/DropdownMenu/Dropdown-menu';
import { ChevronRight, Ellipsis } from 'lucide-react';
import { Ellipsis, Settings, Users } from 'lucide-react';
import { Button } from '@components/ui/Buttons/Button';
import { Team } from '@/types/TeamType';
import Link from 'next/link';

export const columns: ColumnDef<Team>[] = [
{
Expand Down Expand Up @@ -51,6 +52,7 @@ export const columns: ColumnDef<Team>[] = [
id: 'actions',
cell: ({ row }) => {
const team = row.original;

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
Expand All @@ -59,20 +61,22 @@ export const columns: ColumnDef<Team>[] = [
<Ellipsis className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="p-2">
<DropdownMenuContent align="end" className="space-y-1">
{team.role === 'ADMIN' && (
<DropdownMenuItem>
<div className="flex w-full items-center justify-between">
Team settings
<ChevronRight className="ml-8 h-4 w-4" />
<div className="flex w-full items-center">
<Settings className="mr-2 h-4 w-4" />
Edit team
</div>
</DropdownMenuItem>
)}
<DropdownMenuItem>
<div className="flex w-full items-center justify-between">
Team members
<ChevronRight className="ml-8 h-4 w-4" />
</div>
<Link href={`/team/${team.id}/members`}>
<div className="flex w-full items-center justify-between">
<Users className="mr-2 h-4 w-4" />
Show members
</div>
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Expand Down
10 changes: 6 additions & 4 deletions components/Header/Header.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ const getBreadcrumbs = (pathname: string | undefined): Breadcrumb[] => {
}
const breadcrumbs = pathname.split('/').filter((crumb) => crumb);

const breadcrumbItems = breadcrumbs.map((crumb, index) => ({
label: crumb.toUpperCase(),
href: `/${breadcrumbs.slice(0, index + 1).join('/')}`,
}));
const breadcrumbItems = breadcrumbs
.filter((crumb) => !/^\d+$/.test(crumb))
.map((crumb) => ({
label: crumb.toUpperCase(),
href: `/${breadcrumbs.slice(0, breadcrumbs.indexOf(crumb) + 1).join('/')}`,
}));

return [{ label: 'HOME', href: '/' }, ...breadcrumbItems];
};
Expand Down
Loading

0 comments on commit 413290b

Please sign in to comment.