Skip to content

Commit

Permalink
Create GroupingsTable.tsx component
Browse files Browse the repository at this point in the history
  • Loading branch information
FeimeiChen committed Sep 4, 2024
1 parent 3a420dc commit adc1c62
Show file tree
Hide file tree
Showing 26 changed files with 1,633 additions and 42 deletions.
7 changes: 7 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@
"test:watch": "jest --watch"
},
"dependencies": {
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^1.0.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.1.2",
"@tanstack/react-table": "^8.20.1",
"camaro": "^6.2.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
Expand Down
28 changes: 23 additions & 5 deletions ui/src/app/groupings/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
const Groupings = () => {
import GroupingsTable from '@/components/table/GroupingsTable';
import {ownerGroupings} from '@/actions/groupings-api';

const Groupings = async () => {
const res = await ownerGroupings();
const groupingPaths = res.groupingPaths;
return (
<div className="bg-white">
<div className="container">{/* GroupingsTable goes here */}</div>
</div>
<main>
<div className="bg-seafoam pt-3">
<div className="container">
<h1 className="mb-1 font-bold text-[2rem] text-center md:text-left">Manage My Groupings</h1>
<p className="pb-3 text-xl text-center md:text-left">
View and manage groupings I own. Manage members,
configure grouping options and sync destinations.
</p>
</div>
<div className="bg-white">
<div className="container">
<GroupingsTable data={groupingPaths}/>
</div>
</div>
</div>
</main>
);
};
}

export default Groupings;
18 changes: 13 additions & 5 deletions ui/src/components/layout/navbar/navbar-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
'use client';

import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
import { Sheet, SheetContent, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
import User from '@/access/user';
import Link from 'next/link';
import { NavbarLinks } from './navbar-links';
import { useState } from 'react';
import React, { useState } from 'react';
import Role from '@/access/role';
import NavbarMenuIcon from './navbar-menu-icon';
import {VisuallyHidden} from "@radix-ui/react-visually-hidden";

const NavbarMenu = ({ currentUser }: { currentUser: User }) => {
const [open, setOpen] = useState(false);
Expand All @@ -20,13 +21,20 @@ const NavbarMenu = ({ currentUser }: { currentUser: User }) => {
<NavbarMenuIcon open={open} aria-hidden="true" />
</SheetTrigger>

<SheetContent className="mt-[3.9rem] text-xl pt-5 lg:hidden" side="top" onClickOutside={handleClick}>
<SheetContent
className="mt-[3.9rem] text-xl pt-5 lg:hidden"
side="top"
aria-describedby="menu-description"
onClickOutside={handleClick}>
<VisuallyHidden>
<SheetTitle>Main Navigation Menu</SheetTitle>
</VisuallyHidden>
<nav className="container flex flex-col space-y-5 h-1/4">
{NavbarLinks
.filter((navbarLink) =>
currentUser.roles.includes(Role.ADMIN) ||
currentUser.roles.includes(Role.ADMIN) ||
currentUser.roles.includes(navbarLink.role))
.map((navbarLink) =>
.map((navbarLink) =>
<Link href={navbarLink.link} key={navbarLink.name} className="hover:text-uh-teal">
{navbarLink.name}
</Link>)}
Expand Down
132 changes: 132 additions & 0 deletions ui/src/components/table/GroupingsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
'use client';
import {
useReactTable,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getFilteredRowModel,
getSortedRowModel,
SortingState
} from '@tanstack/react-table';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import ColumnSettings from '@/components/table/table-element/ColumnSettings';
import GroupingsTableHeaders from '@/components/table/table-element/GroupingsTableHeaders';
import PaginationBar from '@/components/table/table-element/Pagination';
import GlobalFilter from '@/components/table/table-element/GlobalFilter';
import SortArrow from '@/components/table/table-element/SortArrow';
import { useState } from 'react';
import { SquarePen } from 'lucide-react';
import GroupingPathCell from '@/components/table/table-element/GroupingPathCell';
import { GroupingPath } from '@/models/groupings-api-results';

interface GroupingTableProps {
data: GroupingPath[];
}

const GroupingsTable = ({ data }: GroupingTableProps) => {
const [globalFilter, setGlobalFilter] = useState('');
const [sorting, setSorting] = useState<SortingState>([]);

const table = useReactTable({
columns: GroupingsTableHeaders,
data: data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
state: { globalFilter, sorting },
initialState: {pagination: {pageSize: 20}},
onGlobalFilterChange: setGlobalFilter,
onSortingChange: setSorting
});

const columnCount = table.getHeaderGroups()[0].headers.length;

return (
<div className="px-2">
<div className="flex flex-col md:flex-row md:justify-between pt-5 mb-4">
<h1 className="text-[2rem] font-medium text-text-color text-center pt-3">Manage Groupings</h1>
<div className="flex items-center space-x-2">
<GlobalFilter filter={globalFilter} setFilter={setGlobalFilter}/>
<div className="hidden sm:block">
<ColumnSettings table={table}/>
</div>
</div>
</div>
<Table className="relative overflow-x-auto">
<TableHeader>
{table.getHeaderGroups().map(headerGroup => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header, index) => (
<TableHead
key={header.id}
onClick={header.column.getToggleSortingHandler()}
className={`font-semibold text-uh-black border-solid
border-t-[1px] border-b-[2px] py-3 size-[0.1rem] ${
columnCount === 2 && index === 1 ? 'w-2/3' : 'w-1/3'
} ${header.column.id !== 'GROUPING NAME' ? 'hidden sm:table-cell' : ''}`}
>
<div className="flex items-center text-[0.8rem] font-bold">
{flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getIsSorted() && (
<SortArrow direction={header.column.getIsSorted()}/>
)}
</div>
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row, index) => (
<TableRow key={row.id} className={index % 2 === 0 ? 'bg-light-grey' : ''}>
{row.getVisibleCells().map(cell => (
<TableCell
key={cell.id}
className={`p-0 ${cell.column.id !== 'GROUPING NAME' ?
'hidden sm:table-cell' : ''}`}
width={cell.column.columnDef.size}
>
<div className="flex items-center pl-2 pr-2 text-[15.5px]
overflow-hidden whitespace-nowrap">
<div className="m-2">
{cell.column.id === 'GROUPING NAME' && (
<div className="flex">
<SquarePen className="text-text-primary w-[1.25em] h-[1.25em]"
data-testid={`square-pen-icon-${row.id}`}/>
<div className="text-table-text text-[1rem] pl-2">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</div>
</div>
)}
</div>

{cell.column.id === 'DESCRIPTION' && (
<div
className={`text-table-text text-[1rem]
${columnCount === 3 ?
'truncate sm:max-w-[calc(6ch+1em)] md:max-w-none' : ''}`}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</div>
)}

{cell.column.id === 'GROUPING PATH' && (
<GroupingPathCell data={cell.row.getValue('GROUPING PATH')}
uniqueId={cell.row.id}/>
)}
</div>
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
<PaginationBar table={table}/>
</div>
);
};

export default GroupingsTable;



78 changes: 78 additions & 0 deletions ui/src/components/table/table-element/ColumnSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use client';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { useState } from 'react';
import { GroupingPath } from '@/models/groupings-api-results';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem
} from '@/components/ui/dropdown-menu';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSliders } from '@fortawesome/free-solid-svg-icons';
import {Table} from '@tanstack/table-core';

interface ToggleProps {
table: Table<GroupingPath>;
}
const ColumnSettings = ({ table } : ToggleProps) => {

interface ColumnVisibilityState {
DESCRIPTION: boolean;
'GROUPING PATH': boolean;
}

const [columnVisibility, setColumnVisibility] = useState<ColumnVisibilityState>({
DESCRIPTION: true,
'GROUPING PATH': true,
})

const toggleColumnVisibility = (columnKey: keyof ColumnVisibilityState) => (checked: boolean) => {
setColumnVisibility((prevState) => {
const newState = checked;
table.getColumn(columnKey)?.toggleVisibility(newState);
return { ...prevState, [columnKey]: newState };
});
};

return (
<div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline"
className="border border-gray-300 hover:bg-transparent"
data-testid="column-settings-button">
<FontAwesomeIcon icon={faSliders} className="w-5 h-5 text-text-color"/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuRadioGroup>
<DropdownMenuRadioItem value="description" className="px-2">
<div className="flex items-center space-x-2">
<Switch id="description" checked={columnVisibility.DESCRIPTION}
onCheckedChange={toggleColumnVisibility('DESCRIPTION')}
className="data-[state=checked]:bg-uh-teal" data-testid="description-switch" />
<Label htmlFor="description">Description</Label>
</div>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="grouping path" className="px-2">
<div className="flex items-center space-x-2">
<Switch id="grouping-path" checked={columnVisibility['GROUPING PATH']}
onCheckedChange={toggleColumnVisibility('GROUPING PATH')}
className="data-[state=checked]:bg-uh-teal"
data-testid="grouping-path-switch"/>
<Label htmlFor="grouping-path">Grouping Path</Label>
</div>
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};

export default ColumnSettings;

18 changes: 18 additions & 0 deletions ui/src/components/table/table-element/GlobalFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Input } from '@/components/ui/input'
import { Dispatch, SetStateAction } from 'react';

interface FilterProps {
filter: string;
setFilter: Dispatch<SetStateAction<string>>;
}
const GlobalFilter = ( {filter, setFilter} : FilterProps) => (
<Input
placeholder='Filter Groupings...'
value={filter || ''}
onChange={e => setFilter(e.target.value)}
/>

);

export default GlobalFilter;

53 changes: 53 additions & 0 deletions ui/src/components/table/table-element/GroupingPathCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ClipboardIcon } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { useState } from 'react';

const GroupingPathCell = ({data, uniqueId}: { data: string, uniqueId: string }) => {
const [tooltipContent, setTooltipContent] = useState('copy');
const [tooltipVisible, setTooltipVisible] = useState(false);

const handleClick = async () => {
await navigator.clipboard.writeText(data);
setTooltipContent('copied!');
setTooltipVisible(true);

setTimeout(() => {
setTooltipContent('copy');
setTooltipVisible(false);
}, 2000);
};

return (
<div className="flex items-center w-full outline outline-1 rounded h-6 m-1">
<Input
id={`dataInput-${uniqueId}`}
value={data}
readOnly
className="flex-1 h-6 text-input-text-grey text-[0.875rem]
border-none rounded-none w-[161] truncate"
/>
<TooltipProvider>
<Tooltip open={tooltipVisible} onOpenChange={setTooltipVisible}>
<TooltipTrigger asChild>
<button
onClick={handleClick}
className="relative flex-shrink-0 flex items-center
justify-center hover:bg-green-blue h-6 p-2"
data-testid={`clipboard-button-${uniqueId}`}
>
<ClipboardIcon className="h-4 w-4 text-gray-600"
data-testid={`clipboard-icon-${uniqueId}`}/>
</button>
</TooltipTrigger>
<TooltipContent>
<p data-testid="tooltip">{tooltipContent}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
};

export default GroupingPathCell;

20 changes: 20 additions & 0 deletions ui/src/components/table/table-element/GroupingsTableHeaders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const GroupingsTableHeaders = [
{
header: 'GROUPING NAME',
accessorKey: 'name',
id: 'GROUPING NAME'
},
{
header: 'DESCRIPTION',
accessorKey: 'description',
id: 'DESCRIPTION',
},
{
header: 'GROUPING PATH',
accessorKey: 'path',
id: 'GROUPING PATH',
}
];

export default GroupingsTableHeaders;

Loading

0 comments on commit adc1c62

Please sign in to comment.