-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement grouping-members-table.tsx
- Loading branch information
Showing
66 changed files
with
1,560 additions
and
345 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
ui/src/app/groupings/[groupingPath]/@tab/_components/grouping-members-tab.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import GroupingMembersTable, { | ||
GroupingMembersTableSearchParams | ||
} from './grouping-members-table/grouping-members-table'; | ||
import SortBy from '@/app/groupings/[groupingPath]/@tab/_components/grouping-members-table/table-element/sort-by'; | ||
import { getGroupingMembers } from '@/lib/actions'; | ||
import { Group } from '@/lib/types'; | ||
import { parseAsBoolean, parseAsInteger, parseAsStringEnum } from 'nuqs/server'; | ||
|
||
const size = parseInt(process.env.NEXT_PUBLIC_PAGE_SIZE as string); | ||
|
||
const GroupingMembersTab = async ({ | ||
params, | ||
searchParams, | ||
group | ||
}: { | ||
params: { groupingPath: string }; | ||
searchParams: GroupingMembersTableSearchParams; | ||
group?: Group; | ||
}) => { | ||
const groupingPath = decodeURIComponent(params.groupingPath); | ||
|
||
const page = parseAsInteger.withDefault(1).parseServerSide(searchParams.page); | ||
const sortBy = parseAsStringEnum<SortBy>(Object.values(SortBy)) | ||
.withDefault(SortBy.NAME) | ||
.parseServerSide(searchParams.sortBy); | ||
const isAscending = parseAsBoolean.withDefault(true).parseServerSide(searchParams.isAscending); | ||
const searchString = searchParams.search; | ||
|
||
const groupPath = group ? groupingPath + ':' + group : groupingPath; | ||
const groupingGroupMembers = await getGroupingMembers(groupPath, { | ||
page, | ||
size, | ||
sortBy, | ||
isAscending, | ||
searchString | ||
}); | ||
|
||
return ( | ||
<GroupingMembersTable groupingGroupMembers={groupingGroupMembers} groupingPath={groupingPath} group={group} /> | ||
); | ||
}; | ||
|
||
export default GroupingMembersTab; |
54 changes: 54 additions & 0 deletions
54
...groupingPath]/@tab/_components/grouping-members-table/grouping-members-table-skeleton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { Skeleton } from '@/components/ui/skeleton'; | ||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; | ||
import GroupingMembersTableColumns from './table-element/grouping-members-table-columns'; | ||
import { Group } from '@/lib/types'; | ||
|
||
const pageSize = parseInt(process.env.NEXT_PUBLIC_PAGE_SIZE as string); | ||
|
||
const GroupingMembersTableSkeleton = ({ group }: { group?: Group }) => { | ||
return ( | ||
<div className="px-4 py-1"> | ||
<div className="flex flex-col md:flex-row md:justify-between"> | ||
<h1 className="font-bold text-[32px] capitalize">{group ?? 'All Members'}</h1> | ||
<div className="md:w-60 lg:w-72"> | ||
<Skeleton className="h-10 w-72 rounded" /> | ||
</div> | ||
</div> | ||
<Table className="table-fixed mb-4"> | ||
<TableHeader> | ||
<TableRow> | ||
{GroupingMembersTableColumns().map((column, index) => ( | ||
<TableHead | ||
key={`header-${column}-${index}`} | ||
className={`pl-[0.5rem] | ||
${index > 0 ? 'hidden sm:table-cell' : ''}`} | ||
> | ||
<Skeleton className="h-4 w-36 rounded" /> | ||
</TableHead> | ||
))} | ||
</TableRow> | ||
</TableHeader> | ||
<TableBody> | ||
{Array.from(Array(pageSize), (_, index) => ( | ||
<TableRow key={`row-${index}`}> | ||
{GroupingMembersTableColumns().map((column, index) => ( | ||
<TableCell | ||
key={`cell-${column}-${index}`} | ||
className={`p-[0.5rem] | ||
${index > 0 ? 'hidden sm:table-cell' : ''}`} | ||
> | ||
<Skeleton className="h-4 w-48 rounded" /> | ||
</TableCell> | ||
))} | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
</Table> | ||
<div className="float-end"> | ||
<Skeleton className="h-10 w-80 rounded" /> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default GroupingMembersTableSkeleton; |
195 changes: 195 additions & 0 deletions
195
...oupings/[groupingPath]/@tab/_components/grouping-members-table/grouping-members-table.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
'use client'; | ||
|
||
import { | ||
flexRender, | ||
functionalUpdate, | ||
getCoreRowModel, | ||
PaginationState, | ||
SortingState, | ||
Updater, | ||
useReactTable | ||
} from '@tanstack/react-table'; | ||
import GroupingMembersTableColumns from './table-element/grouping-members-table-columns'; | ||
import { Group, GroupingGroupMembers } from '@/lib/types'; | ||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '@/components/ui/table'; | ||
import SortArrow from '@/components/table/table-element/sort-arrow'; | ||
import PaginationBar from '@/components/table/table-element/pagination-bar'; | ||
import { useTransition } from 'react'; | ||
import GlobalFilter from '@/components/table/table-element/global-filter'; | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { Spinner } from '@/components/ui/spinner'; | ||
import { getGroupingMembersIsBasis, getGroupingMembersWhereListed, getNumberOfGroupingMembers } from '@/lib/actions'; | ||
import { | ||
parseAsBoolean, | ||
parseAsInteger, | ||
parseAsString, | ||
parseAsStringEnum, | ||
useQueryState, | ||
UseQueryStateOptions, | ||
useQueryStates | ||
} from 'nuqs'; | ||
import SortBy, { | ||
findSortBy | ||
} from '@/app/groupings/[groupingPath]/@tab/_components/grouping-members-table/table-element/sort-by'; | ||
|
||
const pageSize = parseInt(process.env.NEXT_PUBLIC_PAGE_SIZE as string); | ||
|
||
export type GroupingMembersTableSearchParams = { | ||
page: string; | ||
sortBy: string; | ||
isAscending: string; | ||
search?: string; | ||
}; | ||
|
||
const GroupingMembersTable = ({ | ||
groupingGroupMembers, | ||
groupingPath, | ||
group | ||
}: { | ||
groupingGroupMembers: GroupingGroupMembers; | ||
groupingPath: string; | ||
group?: Group; | ||
}) => { | ||
const { members, size } = groupingGroupMembers; | ||
const queryStateOptions: Omit<UseQueryStateOptions<string>, 'parse'> = { | ||
history: 'replace', | ||
scroll: false, | ||
shallow: false | ||
}; | ||
|
||
// Pagination. | ||
const [page, setPage] = useQueryState('page', parseAsInteger.withOptions(queryStateOptions).withDefault(1)); | ||
const pagination: PaginationState = { | ||
pageIndex: page - 1, | ||
pageSize | ||
}; | ||
const onPaginationChange = (updater: Updater<PaginationState>) => { | ||
const updatedPagination = functionalUpdate(updater, pagination); | ||
setPage(updatedPagination.pageIndex + 1); | ||
}; | ||
|
||
// Sorting. | ||
const [sort, setSort] = useQueryStates({ | ||
sortBy: parseAsStringEnum<SortBy>(Object.values(SortBy)) | ||
.withOptions(queryStateOptions) | ||
.withDefault(SortBy.NAME), | ||
isAscending: parseAsBoolean.withOptions(queryStateOptions).withDefault(true) | ||
}); | ||
const sorting: SortingState = [{ id: sort.sortBy, desc: !sort.isAscending }]; | ||
const onSortingChange = (updater: Updater<SortingState>) => { | ||
const updatedSorting = functionalUpdate(updater, sorting); | ||
const { id, desc } = updatedSorting[0]; | ||
setSort({ | ||
sortBy: findSortBy(id), | ||
isAscending: !desc | ||
}); | ||
}; | ||
|
||
// Global Filter. | ||
const [isSearchLoading, startTransition] = useTransition(); | ||
const [globalFilter, setGlobalFilter] = useQueryState( | ||
'search', | ||
parseAsString.withOptions({ ...queryStateOptions, startTransition, throttleMs: 200 }).withDefault('') | ||
); | ||
const onGlobalFilterChange = () => { | ||
setPage(1); | ||
}; | ||
|
||
// Fetch number of grouping members. | ||
const groupPath = group ? groupingPath + ':' + group : groupingPath; | ||
const { data: rowCount = Infinity, isPending: isRowCountPending } = useQuery({ | ||
queryKey: [groupPath, 'rowCount'], | ||
queryFn: () => getNumberOfGroupingMembers(groupPath) | ||
}); | ||
|
||
const uhUuids = members.map((member) => member.uhUuid); | ||
|
||
// Fetch isBasis data. | ||
const { data: groupingMembersIsBasis = members, isPending: isBasisPending } = useQuery({ | ||
staleTime: Infinity, // The basis group members rarely change. | ||
enabled: members.length > 0 && !['basis', 'owners', undefined].includes(group), | ||
queryKey: [`${groupingPath}:basis`, uhUuids], | ||
queryFn: () => getGroupingMembersIsBasis(groupingPath, uhUuids).then((res) => res.members) | ||
}); | ||
|
||
// Fetch whereListed data. | ||
const { data: groupingMembersWhereListed = members, isPending: isWhereListedPending } = useQuery({ | ||
enabled: members.length > 0 && !group, | ||
queryKey: [groupingPath, uhUuids], | ||
queryFn: () => getGroupingMembersWhereListed(groupingPath, uhUuids).then((res) => res.members) | ||
}); | ||
|
||
const table = useReactTable({ | ||
columns: GroupingMembersTableColumns(group, !group ? isWhereListedPending : isBasisPending), | ||
data: !group ? groupingMembersWhereListed : groupingMembersIsBasis, | ||
rowCount: globalFilter ? size : rowCount, | ||
getCoreRowModel: getCoreRowModel(), | ||
onPaginationChange, | ||
onSortingChange, | ||
state: { pagination, sorting }, | ||
manualPagination: true, | ||
manualSorting: true, | ||
enableSortingRemoval: false | ||
}); | ||
|
||
return ( | ||
<div className="px-4 py-1"> | ||
<div className="flex flex-col md:flex-row md:justify-between"> | ||
<h1 className="flex font-bold text-[32px] capitalize"> | ||
{group ?? 'All Members'} {!isRowCountPending ? `(${rowCount})` : <Spinner className="ml-2" />} | ||
</h1> | ||
<div className="md:w-60 lg:w-72"> | ||
<GlobalFilter | ||
placeholder="Filter Members..." | ||
filter={globalFilter} | ||
setFilter={setGlobalFilter} | ||
onFilterChange={onGlobalFilterChange} | ||
isLoading={isSearchLoading} | ||
/> | ||
</div> | ||
</div> | ||
<Table className="table-fixed"> | ||
<TableHeader> | ||
{table.getHeaderGroups().map((headerGroup) => ( | ||
<TableRow key={headerGroup.id}> | ||
{headerGroup.headers.map((header) => ( | ||
<TableHead | ||
key={header.id} | ||
onClick={header.column.getToggleSortingHandler()} | ||
className={` | ||
${!table.getIsAllColumnsVisible() && header.column.getIndex() > 0 ? '' : ''} | ||
${header.column.getIndex() > 0 ? 'hidden sm:table-cell' : 'w-2/5 md:w-1/3'} | ||
`} | ||
> | ||
<div className="flex items-center"> | ||
{flexRender(header.column.columnDef.header, header.getContext())} | ||
<SortArrow direction={header.column.getIsSorted()} /> | ||
</div> | ||
</TableHead> | ||
))} | ||
</TableRow> | ||
))} | ||
</TableHeader> | ||
<TableBody> | ||
{table.getRowModel().rows.map((row) => ( | ||
<TableRow key={row.id}> | ||
{row.getVisibleCells().map((cell) => ( | ||
<TableCell | ||
key={cell.id} | ||
className={`${cell.column.getIndex() > 0 ? 'hidden sm:table-cell' : ''}`} | ||
> | ||
<div className="flex items-center px-5 py-1.5 overflow-hidden whitespace-nowrap"> | ||
{flexRender(cell.column.columnDef.cell, cell.getContext())} | ||
</div> | ||
</TableCell> | ||
))} | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
</Table> | ||
<PaginationBar table={table} /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default GroupingMembersTable; |
8 changes: 8 additions & 0 deletions
8
...]/@tab/_components/grouping-members-table/table-element/grouping-member-is-basis-cell.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Skeleton } from '@/components/ui/skeleton'; | ||
import { WhereListed } from '@/lib/types'; | ||
|
||
const GroupingMemberIsBasisCell = ({ whereListed, isPending }: { whereListed: WhereListed; isPending?: boolean }) => { | ||
return <>{!isPending ? <i>{whereListed === 'Basis' ? 'Yes' : 'No'}</i> : <Skeleton className="h-5 w-7" />}</>; | ||
}; | ||
|
||
export default GroupingMemberIsBasisCell; |
37 changes: 37 additions & 0 deletions
37
...Path]/@tab/_components/grouping-members-table/table-element/grouping-member-name-cell.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { isDepartmental } from '@/lib/access/authorization'; | ||
import { faSchool } from '@fortawesome/free-solid-svg-icons'; | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'; | ||
|
||
const GroupingMemberNameCell = ({ name, uid, uhUuid }: { name: string; uid: string; uhUuid: string }) => { | ||
return ( | ||
<> | ||
{name}{' '} | ||
{isDepartmental(uid, uhUuid) && ( | ||
<TooltipProvider> | ||
<Tooltip> | ||
<TooltipTrigger> | ||
<div | ||
className={`ml-1 bg-blue-background rounded-full flex justify-center items-center | ||
h-6 w-6`} | ||
> | ||
<FontAwesomeIcon | ||
icon={faSchool} | ||
size="sm" | ||
aria-label="Departmental Account Icon" | ||
inverse | ||
/> | ||
</div> | ||
</TooltipTrigger> | ||
<TooltipContent className="max-w-52 text-center text-wrap" side="right"> | ||
This is a departmental account, not a personal account. Departmental accounts are often | ||
shared by multiple individuals and used for external communications. | ||
</TooltipContent> | ||
</Tooltip> | ||
</TooltipProvider> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
export default GroupingMemberNameCell; |
30 changes: 30 additions & 0 deletions
30
...gPath]/@tab/_components/grouping-members-table/table-element/grouping-member-uid-cell.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'; | ||
|
||
const GroupingMemberUidCell = ({ uid }: { uid: string }) => { | ||
return ( | ||
<> | ||
{uid ? ( | ||
uid | ||
) : ( | ||
<span className="text-text-color"> | ||
N/A{' '} | ||
<TooltipProvider> | ||
<Tooltip> | ||
<TooltipTrigger> | ||
<FontAwesomeIcon icon={faQuestionCircle} color="black" /> | ||
</TooltipTrigger> | ||
<TooltipContent className="max-w-48 text-center whitespace-normal" side="right"> | ||
UH Username not available. Either it has not yet been assigned, or the subject is no | ||
longer with UH. | ||
</TooltipContent> | ||
</Tooltip> | ||
</TooltipProvider> | ||
</span> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
export default GroupingMemberUidCell; |
Oops, something went wrong.