Skip to content

Commit

Permalink
DEAR-125: add TeamTable components
Browse files Browse the repository at this point in the history
  • Loading branch information
baurnick committed Jul 19, 2024
1 parent 688e80e commit 9584d2b
Show file tree
Hide file tree
Showing 15 changed files with 583 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
node-version: '22.4.1'

- name: Install dependencies
run: npm install
Expand Down
85 changes: 85 additions & 0 deletions app/(main)/team/components/TeamTable/TeamTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* eslint-disable react/prop-types */

'use client';

import * as React from 'react';
import {
ColumnDef,
ColumnFiltersState,
flexRender,
getCoreRowModel,
getSortedRowModel,
getFilteredRowModel,
SortingState,
useReactTable,
VisibilityState,
} from '@tanstack/react-table';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@components/ui/Table/Table';
import TeamTableToolbar from './TeamTableToolbar';

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

const TeamTable = <TData,>({ columns, data }: TeamTableProps<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">
<TeamTableToolbar 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 TeamTable;
25 changes: 25 additions & 0 deletions app/(main)/team/components/TeamTable/TeamTableToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
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> {
table: Table<TData>;
}

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

export default TeamTableToolbar;
43 changes: 43 additions & 0 deletions app/(main)/team/components/TeamTable/TeamTableViewOptions.tsx
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 TeamTableViewOptionsProps<TData> {
table: Table<TData>;
}

const TeamTableViewOptions = <TData,>({ table }: TeamTableViewOptionsProps<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 TeamTableViewOptions;
82 changes: 82 additions & 0 deletions app/(main)/team/components/TeamTable/columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* eslint-disable @typescript-eslint/no-redeclare */

'use client';

import { Badge } from '@components/ui/Badge/Badge';
import DataTableColumnHeader from '@components/ui/Table/DataTableColumnHeader';
import { ColumnDef } from '@tanstack/react-table';
import cn from '@/lib/utils';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from '@components/ui/DropdownMenu/Dropdown-menu';
import { ChevronRight, Ellipsis } from 'lucide-react';
import { Button } from '@components/ui/Buttons/Button';

export type TeamType = {
id: string;
name: string;
created: string;
role: 'ADMIN' | 'MEMBER';
};

export const columns: ColumnDef<TeamType>[] = [
{
accessorKey: 'id',
header: '',
},
{
accessorKey: 'name',
header: ({ column }) => <DataTableColumnHeader column={column} title="Name" />,
},
{
accessorKey: 'created',
header: ({ column }) => <DataTableColumnHeader column={column} title="Created" />,
},
{
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: 'actions',
cell: ({ row }) => {
const team = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<Ellipsis className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="p-2">
{team.role === 'ADMIN' && (
<DropdownMenuItem>
<div className="flex w-full items-center justify-between">
Team settings
<ChevronRight className="ml-8 h-4 w-4" />
</div>
</DropdownMenuItem>
)}
<DropdownMenuItem>
<div className="flex w-full items-center justify-between">
Team members
<ChevronRight className="ml-8 h-4 w-4" />
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
28 changes: 28 additions & 0 deletions app/(main)/team/components/TeamTable/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable import/prefer-default-export */
type TeamTable = {
id: string;
name: string;
created: string;
role: 'ADMIN' | 'MEMBER';
};

export const teams: TeamTable[] = [
{
id: '1',
name: 'Team 1',
created: 'Team Member 1',
role: 'ADMIN',
},
{
id: '2',
name: 'Team 2',
created: 'Team Member 2',
role: 'MEMBER',
},
{
id: '3',
name: 'Team 3',
created: 'Team Member 2',
role: 'MEMBER',
},
];
19 changes: 19 additions & 0 deletions app/(main)/team/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 TeamLayoutProps {
children: React.ReactNode;
}

const TeamLayout: React.FC<TeamLayoutProps> = ({ children }) => (
<div className="space-y-8 px-16 pb-16">
<div className="space-y-0.5">
<h1>Team</h1>
<p className="text-md font-thin">Manage your teams and invite new members to join your team.</p>
</div>
<Separtor className="dark:border-secondaryBG-dark" />
<div>{children}</div>
</div>
);

export default TeamLayout;
12 changes: 12 additions & 0 deletions app/(main)/team/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from 'react';
import { columns, TeamType } from './components/TeamTable/columns';
import { teams } from './components/TeamTable/data';
import TeamTable from './components/TeamTable/TeamTable';

const TeamPage: React.FC = () => (
<div className="space-y-4">
<TeamTable<TeamType> columns={columns} data={teams} />
</div>
);

export default TeamPage;
5 changes: 4 additions & 1 deletion components/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import Link from 'next/link';
import { Home, Settings, BarChartHorizontalBig, Info, CircleHelp } from 'lucide-react';
import { Home, Settings, BarChartHorizontalBig, Info, CircleHelp, Users } from 'lucide-react';
import NavLink from '@components/Navigation/NavLink';
import Logo from '@components/ui/Logo/Logo';

Expand All @@ -18,6 +18,9 @@ const Navigation: React.FC = () => (
<NavLink href="/insights">
<BarChartHorizontalBig className="h-5 w-5 text-black dark:text-white" />
</NavLink>
<NavLink href="/team">
<Users className="h-5 w-5 text-black dark:text-white" />
</NavLink>
</nav>
<nav className="flex flex-col items-center gap-6 px-2">
<NavLink href="/contact">
Expand Down
32 changes: 32 additions & 0 deletions components/ui/Badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';

import cn from '@/lib/utils';

const badgeVariants = cva(
'inline-flex items-center rounded-full border border-slate-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 dark:border-slate-800 dark:focus:ring-slate-300',
{
variants: {
variant: {
default:
'border-transparent bg-slate-900 text-slate-50 hover:bg-slate-900/80 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/80',
secondary:
'border-transparent bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80',
destructive:
'border-transparent bg-red-500 text-slate-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-slate-50 dark:hover:bg-red-900/80',
outline: 'text-slate-950 dark:text-slate-50',
},
},
defaultVariants: {
variant: 'default',
},
}
);

export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
}

export { Badge, badgeVariants };
Loading

0 comments on commit 9584d2b

Please sign in to comment.