-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add PersonTable #114
base: main
Are you sure you want to change the base?
Add PersonTable #114
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'use client'; | ||
|
||
import Link from 'next/link'; | ||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; | ||
import { usePathname } from 'next/navigation'; | ||
|
||
const AdminTabsLayout = ({ children }: { children: React.ReactNode }) => { | ||
const currentPath = usePathname().split('/').pop(); | ||
return ( | ||
<> | ||
<Tabs className="bg-seafoam" value={currentPath === 'admin' ? 'manage-groupings' : currentPath.toString()}> | ||
<div className="container"> | ||
<TabsList variant="outline"> | ||
<Link key={'groupings'} href={`/admin/manage-groupings`}> | ||
<TabsTrigger value="manage-groupings" variant="outline"> | ||
Manage Groupings | ||
</TabsTrigger> | ||
</Link> | ||
<Link key={'admins'} href={'/admin/manage-admins'}> | ||
<TabsTrigger value="manage-admins" variant="outline"> | ||
Manage Admins | ||
</TabsTrigger> | ||
</Link> | ||
<Link key={'person'} href={'/admin/manage-person'}> | ||
<TabsTrigger value="manage-person" variant="outline"> | ||
Manage Person | ||
</TabsTrigger> | ||
</Link> | ||
</TabsList> | ||
</div> | ||
</Tabs> | ||
{children} | ||
</> | ||
); | ||
}; | ||
|
||
export default AdminTabsLayout; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const AdminsTab = () => { | ||
return ( | ||
<div className="container"> | ||
<h1>Testing Admins</h1> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AdminsTab; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const GroupingsTab = () => { | ||
return ( | ||
<div className="container"> | ||
<h1>Testing Groupings</h1> | ||
</div> | ||
); | ||
}; | ||
|
||
export default GroupingsTab; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { managePersonResults } from '@/lib/fetchers'; | ||
import PersonTable from '@/app/admin/_components/personTable'; | ||
import { memberAttributeResults } from '@/lib/actions'; | ||
|
||
const PersonTab = async (searchParams) => { | ||
const searchUid = searchParams.searchParams.searchUid; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also I'm not sure why the searchParams object would make you access .searchParams |
||
const groupingsInfo = await managePersonResults(searchUid); | ||
const userInfo = searchUid === undefined ? undefined : (await memberAttributeResults([searchUid])).results[0]; | ||
Comment on lines
+7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's call these variables managePersonResults and memberAttributeResults so it's more clear what exactly we are passing into the PersonTable. |
||
const props = { groupingsInfo: groupingsInfo, userInfo: userInfo, searchUid: searchUid }; | ||
|
||
return ( | ||
<> | ||
<div className="container"> | ||
<PersonTable {...props} /> | ||
Comment on lines
+9
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of defining a props object, follow the other components and how they define each prop individually. |
||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default PersonTab; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
'use client'; | ||
|
||
import { | ||
useReactTable, | ||
flexRender, | ||
getCoreRowModel, | ||
getPaginationRowModel, | ||
getFilteredRowModel, | ||
getSortedRowModel, | ||
SortingState, | ||
RowSelectionState | ||
} from '@tanstack/react-table'; | ||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; | ||
import PaginationBar from '@/components/table/table-element/pagination-bar'; | ||
import GlobalFilter from '@/components/table/table-element/global-filter'; | ||
import SortArrow from '@/components/table/table-element/sort-arrow'; | ||
import { useState } from 'react'; | ||
import { ArrowUpRightFromSquare } from 'lucide-react'; | ||
import { CrownIcon } from 'lucide-react'; | ||
import { Button } from '@/components/ui/button'; | ||
import SearchInput from '@/app/admin/_components/searchInput'; | ||
import personTableColumns from '@/components/table/table-element/person-table-columns'; | ||
import PersonTableTooltip from '@/app/admin/_components/personTableTooltip'; | ||
import Link from 'next/link'; | ||
import { useRouter } from 'next/navigation'; | ||
import { groupingOwners, removeFromGroups } from '@/lib/actions'; | ||
import OwnersModal from '@/components/modal/owners-modal'; | ||
import { Alert, AlertDescription } from '@/components/ui/alert'; | ||
|
||
const pageSize = parseInt(process.env.NEXT_PUBLIC_PAGE_SIZE as string); | ||
|
||
const PersonTable = (data) => { | ||
const [globalFilter, setGlobalFilter] = useState(''); | ||
const [sorting, setSorting] = useState<SortingState>([]); | ||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); | ||
const [modalOpen, setModalOpen] = useState(false); | ||
const [modalData, setModalData] = useState([]); | ||
const [dummyBool, setDummyBool] = useState(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's this dummyBool for? |
||
const router = useRouter(); | ||
const searchUid = data.searchUid; | ||
const validUid = data.groupingsInfo.resultCode; | ||
const groupingsInfo = data.groupingsInfo.results; | ||
const userInfo = data.userInfo; | ||
const hydrateModal = async (path) => { | ||
setModalData((await groupingOwners(path)).members); | ||
setModalOpen(true); | ||
}; | ||
|
||
const close = () => { | ||
setModalOpen(false); | ||
}; | ||
|
||
const handleRemove = async () => { | ||
const numSelected = table.getSelectedRowModel().rows.length; | ||
const arr = []; | ||
let i; | ||
for (i = 0; i < numSelected; i++) { | ||
const original = table.getSelectedRowModel().rows[i].original; | ||
if (original.inOwner) arr.push(original.path + ':owners'); | ||
if (original.inInclude) arr.push(original.path + ':include'); | ||
if (original.inExclude) arr.push(original.path + ':exclude'); | ||
} | ||
Comment on lines
+54
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a very nice way to rewrite this code using the .flatMap function: (Generated by ChatGPT!)
|
||
await removeFromGroups(searchUid, arr); | ||
setRowSelection({}); | ||
router.refresh(); | ||
}; | ||
|
||
const table = useReactTable({ | ||
columns: personTableColumns, | ||
data: groupingsInfo, | ||
getCoreRowModel: getCoreRowModel(), | ||
getPaginationRowModel: getPaginationRowModel(), | ||
getFilteredRowModel: getFilteredRowModel(), | ||
getSortedRowModel: getSortedRowModel(), | ||
state: { globalFilter, sorting, rowSelection }, | ||
initialState: { pagination: { pageSize } }, | ||
onGlobalFilterChange: setGlobalFilter, | ||
onSortingChange: setSorting, | ||
onRowSelectionChange: setRowSelection | ||
}); | ||
|
||
return ( | ||
<> | ||
<OwnersModal open={modalOpen} close={close} modalData={modalData} /> | ||
<div className="flex flex-col md:flex-row md:justify-between pt-1 mb-1"> | ||
<div className="flex items-center"> | ||
<h1 className="text-[2rem] font-medium text-text-color md:justify-start pt-3">Manage Person</h1> | ||
<p className="text-[1.2rem] font-medium text-text-color md:justify-end pt-5 ps-2"> | ||
{userInfo === undefined | ||
? '' | ||
: '(' + userInfo.name + ', ' + userInfo.uid + ', ' + userInfo.uhUuid + ')'} | ||
</p> | ||
Comment on lines
+88
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of the ternary operator, it's nicer to use the && operator:
|
||
</div> | ||
<div className="flex flex-col md:flex-row md:justify-end md:w-72 lg-84 pt-3 mb-1"> | ||
<GlobalFilter filter={globalFilter} setFilter={setGlobalFilter} /> | ||
</div> | ||
</div> | ||
<div className="flex flex-col md:flex-row md:justify-between items-center mb-4"> | ||
<SearchInput /> | ||
<label> | ||
<div> | ||
Check All | ||
<input | ||
className="mx-2" | ||
type="checkbox" | ||
name="checkAll" | ||
checked={userInfo === undefined ? dummyBool : table.getIsAllPageRowsSelected()} | ||
onChange={ | ||
userInfo === undefined | ||
? () => setDummyBool(!dummyBool) | ||
: table.getToggleAllPageRowsSelectedHandler() | ||
} | ||
/> | ||
<Button | ||
className="rounded-[-0.25rem] rounded-r-[0.25rem]" | ||
Comment on lines
+114
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I always thought it was weird that our button is not rounded on the left side. Let's keep the button normal and not straighten the left side, thanks. |
||
variant="destructive" | ||
onClick={userInfo === undefined ? () => void 0 : handleRemove} | ||
> | ||
Remove | ||
</Button> | ||
</div> | ||
</label> | ||
</div> | ||
{validUid === 'FAILURE' && searchUid !== undefined ? ( | ||
<Alert variant="destructive" className="w-fit mb-7"> | ||
<AlertDescription>{searchUid} is not in any grouping.</AlertDescription> | ||
</Alert> | ||
) : ( | ||
'' | ||
)} | ||
{validUid === undefined && searchUid !== '' ? ( | ||
<Alert variant="destructive" className="w-fit mb-7"> | ||
<AlertDescription> | ||
There was an error searching for {searchUid}. <br /> | ||
Please ensure you have entered a valid UH member and try again. | ||
</AlertDescription> | ||
</Alert> | ||
) : ( | ||
'' | ||
)} | ||
{searchUid === '' ? ( | ||
<Alert variant="destructive" className="w-fit mb-7"> | ||
<AlertDescription>You must enter a UH member to search.</AlertDescription> | ||
</Alert> | ||
) : ( | ||
'' | ||
)} | ||
Comment on lines
+124
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same goes for here, use the && operator instead of the ternary. |
||
<Table className="relative overflow-x-auto"> | ||
<TableHeader> | ||
{table.getHeaderGroups().map((headerGroup) => ( | ||
<TableRow key={headerGroup.id}> | ||
{headerGroup.headers.map((header) => ( | ||
<TableHead | ||
key={header.id} | ||
onClick={ | ||
header.column.id === 'name' | ||
? header.column.getToggleSortingHandler() | ||
: () => void 0 | ||
} | ||
Comment on lines
+155
to
+159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should be able to disable sorting for a certain column in your person-table-columns.tsx file. Then you can just use the getToggleSortingHandler() without this ternary, |
||
className={`${header.column.id === 'name' ? 'w-1/4' : 'w-1/12'}`} | ||
> | ||
<div className="flex items-center"> | ||
{flexRender(header.column.columnDef.header, header.getContext())} | ||
<SortArrow direction={header.column.getIsSorted()} /> | ||
</div> | ||
</TableHead> | ||
))} | ||
</TableRow> | ||
))} | ||
</TableHeader> | ||
{userInfo === undefined ? ( | ||
'' | ||
) : ( | ||
Comment on lines
+171
to
+173
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the && operator instead of the ternary, thanks. |
||
<TableBody> | ||
{table.getRowModel().rows.map((row) => ( | ||
<TableRow key={row.id}> | ||
{row.getVisibleCells().map((cell) => ( | ||
<TableCell key={cell.id} width={cell.column.columnDef.size}> | ||
<div className="flex items-center px-2 overflow-hidden whitespace-nowrap"> | ||
<div className={`m-2 ${cell.column.id === 'name' ? 'w-full' : ''}`}> | ||
{cell.column.id === 'name' && ( | ||
<div className="flex flex-row"> | ||
<PersonTableTooltip | ||
value={'Manage grouping.'} | ||
side={'top'} | ||
desc={ | ||
<Link | ||
href={`/groupings/${cell.row.original.path}`} | ||
rel="noopener noreferrer" | ||
target="_blank" | ||
> | ||
<ArrowUpRightFromSquare | ||
size="1.25em" | ||
className="text-text-primary" | ||
data-testid={'arrow-up-right-from-square-icon'} | ||
/> | ||
</Link> | ||
} | ||
></PersonTableTooltip> | ||
Comment on lines
+183
to
+199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's preferred to pass in components as children instead of as a prop. Also the props |
||
<div className="pl-3"> | ||
{flexRender(cell.column.columnDef.cell, cell.getContext())} | ||
</div> | ||
<PersonTableTooltip | ||
value="Display the grouping's owners." | ||
side={'right'} | ||
desc={ | ||
<div className="ml-auto mr-3"> | ||
<CrownIcon | ||
size="1.25em" | ||
className="text-text-primary" | ||
data-testid={'crown-icon'} | ||
onClick={() => | ||
hydrateModal(cell.row.original.path) | ||
} | ||
/> | ||
</div> | ||
} | ||
></PersonTableTooltip> | ||
</div> | ||
)} | ||
</div> | ||
{cell.column.id === 'inOwner' && ( | ||
<div className="ml-1"> | ||
<p className={`${cell.row.original.inOwner ? 'text-red-500' : ''}`}> | ||
{cell.row.original.inOwner ? 'Yes' : 'No'} | ||
</p> | ||
</div> | ||
)} | ||
{cell.column.id === 'inBasisAndInclude' && ( | ||
<div> | ||
<p | ||
className={`${ | ||
cell.row.original.inBasisAndInclude ? 'text-red-500' : '' | ||
}`} | ||
> | ||
{cell.row.original.inBasisAndInclude ? 'Yes' : 'No'} | ||
</p> | ||
</div> | ||
)} | ||
{cell.column.id === 'inInclude' && ( | ||
<div className="ml-2"> | ||
<p | ||
className={`${ | ||
cell.row.original.inInclude ? 'text-red-500' : '' | ||
}`} | ||
> | ||
{cell.row.original.inInclude ? 'Yes' : 'No'} | ||
</p> | ||
</div> | ||
)} | ||
{cell.column.id === 'inExclude' && ( | ||
<div className="ml-2"> | ||
<p | ||
className={`${ | ||
cell.row.original.inExclude ? 'text-red-500' : '' | ||
}`} | ||
> | ||
{cell.row.original.inExclude ? 'Yes' : 'No'} | ||
</p> | ||
</div> | ||
)} | ||
{cell.column.id === 'remove' && ( | ||
<div className="ml-3"> | ||
<input | ||
type="checkbox" | ||
checked={row.getIsSelected()} | ||
onChange={row.getToggleSelectedHandler()} | ||
/> | ||
</div> | ||
)} | ||
Comment on lines
+222
to
+270
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These && statements should be moved to the person-table-columns.tsx file. Reference how the groupings-table-columns.tsx file does it. |
||
</div> | ||
</TableCell> | ||
))} | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
)} | ||
</Table> | ||
{userInfo === undefined ? '' : <PaginationBar table={table} />} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the && operator here too. |
||
</> | ||
); | ||
}; | ||
|
||
export default PersonTable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure to set a type for searchParams, it would probably be:
searchParams: { searchUid: string }
. Also I recommend changing the searchParam fromsearchUid
touhIdentifier
because that naming would cover both uid and uhUuid.