diff --git a/frontend/src/modules/users/Users.tsx b/frontend/src/modules/users/Users.tsx index 51c4bce6befd1b0098542ca3607ce37d8b1b6256..288c175356332c609d774c26718de1cf7817d02e 100644 --- a/frontend/src/modules/users/Users.tsx +++ b/frontend/src/modules/users/Users.tsx @@ -9,8 +9,18 @@ import React, { useState, useCallback, useEffect, useMemo, HTMLProps } from 'react'; // Icons -import { SearchIcon, PlusIcon, ViewGridAddIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/solid'; -import { CogIcon, TrashIcon } from '@heroicons/react/outline'; +import { + SearchIcon, + PlusIcon, + ViewGridAddIcon, + ChevronDownIcon, + ChevronUpIcon, + ChevronDoubleLeftIcon, + ChevronDoubleRightIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from '@heroicons/react/solid'; +import { CogIcon } from '@heroicons/react/outline'; // API - Redux import { useUsers, User } from 'src/services/users'; @@ -18,12 +28,12 @@ import { useAuth } from 'src/services/auth'; import { useApps } from 'src/services/apps'; // Regular Table -import { Table } from 'src/components'; +// import { Table } from 'src/components'; import { debounce } from 'lodash'; // User Table import { - Column, + // Column, ColumnDef, flexRender, getCoreRowModel, @@ -31,7 +41,7 @@ import { getPaginationRowModel, getSortedRowModel, SortingState, - Table as UserTable, + // Table, useReactTable, } from '@tanstack/react-table'; @@ -42,12 +52,12 @@ import { MultipleUsersModal } from './components'; // /////////////////////////////////////////// export const Users: React.FC = () => { - const [selectedRowsIds, setSelectedRowsIds] = useState({}); + // const [selectedRowsIds, setSelectedRowsIds] = useState({}); const [configureModal, setConfigureModal] = useState(false); const [multipleUsersModal, setMultipleUsersModal] = useState(false); const [userId, setUserId] = useState(null); const [search, setSearch] = useState(''); - const { users, loadUsers, userTableLoading } = useUsers(); + const { users, loadUsers } = useUsers(); const { isAdmin } = useAuth(); const { apps, loadApps } = useApps(); @@ -87,90 +97,13 @@ export const Users: React.FC = () => { const multipleUsersModalClose = () => setMultipleUsersModal(false); - const columns: any = React.useMemo( - () => [ - { - Header: 'Name', - accessor: 'name', - width: 'auto', - }, - { - Header: 'Email', - accessor: 'email', - width: 'auto', - }, - { - Header: 'Status', - accessor: 'status', - width: 'auto', - }, - { - Header: ' ', - Cell: (props: any) => { - const { row } = props; - - if (isAdmin) { - return ( - <div className="text-right relative"> - <div className="absolute inline-flex px-2 py-1 text-transparent items-center font-medium border border-transparent right-0 z-0"> - Configure <CogIcon className="-mr-0.5 ml-2 h-4 w-4 text-gray-500" /> - </div> - <button - onClick={() => configureModalOpen(row.original.id)} - type="button" - className="relative z-10 lg:opacity-0 group-hover:opacity-100 transition-opacity inline-flex items-center px-2 py-1 border border-gray-200 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500" - > - Configure <CogIcon className="-mr-0.5 ml-2 h-4 w-4" /> - </button> - </div> - ); - } - - return null; - }, - width: 'auto', - }, - ], - [isAdmin], - ); - - const selectedRows = useCallback((rows: Record<string, boolean>) => { - setSelectedRowsIds(rows); - }, []); + // const selectedRows = useCallback((rows: Record<string, boolean>) => { + // setSelectedRowsIds(rows); + // }, []); // //////////////////////// // New Table Start // //////////////////////// - // function Filter({ column, table }: { column: Column<any, any>; table: UserTable<any> }) { - // const firstValue = table.getPreFilteredRowModel().flatRows[0]?.getValue(column.id); - - // return typeof firstValue === 'number' ? ( - // <div className="flex space-x-2"> - // <input - // type="number" - // value={((column.getFilterValue() as any)?.[0] ?? '') as string} - // onChange={(e) => column.setFilterValue((old: any) => [e.target.value, old?.[1]])} - // placeholder="Min" - // className="w-24 border shadow rounded" - // /> - // <input - // type="number" - // value={((column.getFilterValue() as any)?.[1] ?? '') as string} - // onChange={(e) => column.setFilterValue((old: any) => [old?.[0], e.target.value])} - // placeholder="Max" - // className="w-24 border shadow rounded" - // /> - // </div> - // ) : ( - // <input - // type="text" - // value={(column.getFilterValue() ?? '') as string} - // onChange={(e) => column.setFilterValue(e.target.value)} - // placeholder="Search..." - // className="w-36 border shadow rounded" - // /> - // ); - // } function IndeterminateCheckbox({ indeterminate, @@ -188,14 +121,13 @@ export const Users: React.FC = () => { <input type="checkbox" ref={ref} - className="focus:ring-primary-800 h-4 w-4 text-primary-700 border-gray-300 rounded" + className="focus:ring-primary-800 h-4 w-4 text-primary-700 border-gray-300 rounded cursor-pointer" {...rest} /> ); } function CreateUserTable() { const [rowSelection, setRowSelection] = React.useState({}); - // const [globalFilter, setGlobalFilter] = React.useState(''); const [sorting, setSorting] = React.useState<SortingState>([]); const userColumns = React.useMemo<ColumnDef<User>[]>( () => [ @@ -267,8 +199,6 @@ export const Users: React.FC = () => { [isAdmin], ); - // const [data] = React.useState(() => users); - const table = useReactTable({ data: filterSearch, columns: userColumns, @@ -276,10 +206,14 @@ export const Users: React.FC = () => { rowSelection, sorting, }, + initialState: { + pagination: { + pageSize: 10, + }, + }, onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), enableRowSelection: true, // enable row selection for all rows - // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), @@ -289,155 +223,180 @@ export const Users: React.FC = () => { return ( <> - <table className="min-w-full divide-y divide-gray-200 table-auto"> - <thead className="bg-gray-50"> - {table.getHeaderGroups().map((headerGroup) => ( - <tr key={headerGroup.id}> - {headerGroup.headers.map((header) => { - return ( - <th - key={header.id} - colSpan={header.colSpan} - scope="col" - className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" - > - {header.isPlaceholder ? null : ( - <div - {...{ - className: header.column.getCanSort() ? 'flex items-center' : '', - onClick: header.column.getToggleSortingHandler(), - }} - > - <span> {flexRender(header.column.columnDef.header, header.getContext())}</span> - {{ - asc: <ChevronUpIcon className="w-4 h-4 text-gray-400 ml-1" />, - desc: <ChevronDownIcon className="w-4 h-4 text-gray-400 ml-1" />, - }[header.column.getIsSorted() as string] ?? null} - </div> - )} - </th> - ); - })} - </tr> - ))} - </thead> - <tbody className=""> - {table.getRowModel().rows.map((row, rowIndex) => { - return ( - <tr key={row.id} role="row" className={rowIndex % 2 === 0 ? 'bg-white group' : 'bg-gray-50 group'}> - {row.getVisibleCells().map((cell) => { + <div className="flex justify-between w-100 my-3 items-center mb-5 "> + <div className="flex items-center"> + <div className="inline-block"> + <label htmlFor="email" className="block text-sm font-medium text-gray-700 sr-only"> + Search candidates + </label> + <div className="mt-1 flex rounded-md shadow-sm"> + <div className="relative flex items-stretch flex-grow focus-within:z-10"> + <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> + <SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" /> + </div> + <input + type="text" + name="email" + id="email" + className="focus:ring-primary-500 focus:border-primary-500 block w-full rounded-md pl-10 sm:text-sm border-gray-200" + placeholder="Search everything..." + onChange={debouncedSearch} + /> + </div> + </div> + </div> + </div> + + {Object.keys(rowSelection).length !== 0 && ( + <div className="flex items-center"> + <button + onClick={() => {}} + type="button" + className="inline-flex items-center px-4 py-2 text-sm font-medium rounded-md text-gray-500 bg-primary-50 hover:bg-primary-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 border border-gray-200 shadow-sm" + > + <CogIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" /> + Configure {Object.keys(rowSelection).length} user{Object.keys(rowSelection).length !== 1 ? 's' : null} + </button> + </div> + )} + </div> + <div className="flex justify-between items-center text-xs font-medium text-gray-500 uppercase tracking-wider"> + <div className="py-3 text-left "> + Showing {table.getRowModel().rows.length} of {table.getCoreRowModel().rows.length} entries + </div> + <nav className="isolate inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination"> + <button + className="relative inline-flex items-center rounded-l-md px-2 py-2 ring-1 ring-inset ring-gray-300 transition hover:bg-gray-100 focus:z-20 focus:outline-offset-0" + onClick={() => table.setPageIndex(0)} + disabled={!table.getCanPreviousPage()} + > + <ChevronDoubleLeftIcon className="w-4 h-4" /> + </button> + <button + className="relative inline-flex items-center px-2 py-2 ring-1 ring-inset ring-gray-300 transition hover:bg-gray-100 focus:z-20 focus:outline-offset-0" + onClick={() => table.previousPage()} + disabled={!table.getCanPreviousPage()} + > + <ChevronLeftIcon className="w-4 h-4" /> + </button> + <div className="relative inline-flex items-center px-2 py-2 ring-1 ring-inset ring-gray-300 focus:z-20 focus:outline-offset-0"> + <span> + Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} + </span> + </div> + <button + className="relative inline-flex items-center px-2 py-2 ring-1 ring-inset ring-gray-300 transition hover:bg-gray-100 focus:z-20 focus:outline-offset-0" + onClick={() => table.nextPage()} + disabled={!table.getCanNextPage()} + > + <ChevronRightIcon className="w-4 h-4" /> + </button> + <button + className="relative inline-flex items-center rounded-r-md px-2 py-2 ring-1 ring-inset ring-gray-300 transition hover:bg-gray-100 focus:z-20 focus:outline-offset-0" + onClick={() => table.setPageIndex(table.getPageCount() - 1)} + disabled={!table.getCanNextPage()} + > + <ChevronDoubleRightIcon className="w-4 h-4" /> + </button> + </nav> + <div className="flex items-center gap-2 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> + <span>Results per page</span> + <select + value={table.getState().pagination.pageSize} + className="focus:ring-primary-500 focus:border-primary-500 py-1 block rounded-md text-sm border-gray-200" + onChange={(e) => { + table.setPageSize(Number(e.target.value)); + }} + > + {[10, 20, 50, 100, 250, 500, 10000, table.getCoreRowModel().rows.length].map((pageSize) => + pageSize <= table.getCoreRowModel().rows.length ? ( + <option key={pageSize} value={pageSize}> + {pageSize === table.getCoreRowModel().rows.length ? `all` : `${pageSize}`} + </option> + ) : null, + )} + </select> + </div> + </div> + <div className="shadow border-b border-gray-200 sm:rounded-lg overflow-hidden"> + <table className="min-w-full divide-y divide-gray-200 table-auto"> + <thead className="bg-gray-50"> + {table.getHeaderGroups().map((headerGroup) => ( + <tr key={headerGroup.id}> + {headerGroup.headers.map((header) => { return ( - <td - key={cell.id} - className={ - cell.id.substring(2) === 'select' - ? 'w-4 px-6 py-4 whitespace-nowrap text-sm text-gray-500' - : 'px-6 py-4 whitespace-nowrap text-sm text-gray-500' - } + <th + key={header.id} + colSpan={header.colSpan} + scope="col" + className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" > - {flexRender(cell.column.columnDef.cell, cell.getContext())} - </td> + {header.isPlaceholder ? null : ( + <div + {...{ + className: header.column.getCanSort() ? 'flex items-center' : '', + onClick: header.column.getToggleSortingHandler(), + }} + > + <span> {flexRender(header.column.columnDef.header, header.getContext())}</span> + {{ + asc: <ChevronUpIcon className="w-4 h-4 text-gray-400 ml-1" />, + desc: <ChevronDownIcon className="w-4 h-4 text-gray-400 ml-1" />, + }[header.column.getIsSorted() as string] ?? null} + </div> + )} + </th> ); })} </tr> - ); - })} - </tbody> - <tfoot> - <tr> - <td className="flex items-center"> - <IndeterminateCheckbox - {...{ - checked: table.getIsAllPageRowsSelected(), - indeterminate: table.getIsSomePageRowsSelected(), - onChange: table.getToggleAllPageRowsSelectedHandler(), - }} - /> - </td> - <td colSpan={20}>Page Rows ({table.getRowModel().rows.length})</td> - </tr> - </tfoot> - </table> - <div className="h-2" /> - <div className="flex items-center gap-2"> - <button - className="border rounded p-1" - onClick={() => table.setPageIndex(0)} - disabled={!table.getCanPreviousPage()} - > - {'<<'} - </button> - <button - className="border rounded p-1" - onClick={() => table.previousPage()} - disabled={!table.getCanPreviousPage()} - > - {'<'} - </button> - <button className="border rounded p-1" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}> - {'>'} - </button> - <button - className="border rounded p-1" - onClick={() => table.setPageIndex(table.getPageCount() - 1)} - disabled={!table.getCanNextPage()} - > - {'>>'} - </button> - <span className="flex items-center gap-1"> - <div>Page</div> - <strong> - {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} - </strong> - </span> - <span className="flex items-center gap-1"> - | Go to page: - <input - type="number" - defaultValue={table.getState().pagination.pageIndex + 1} - onChange={(e) => { - const page = e.target.value ? Number(e.target.value) - 1 : 0; - table.setPageIndex(page); - }} - className="focus:ring-primary-500 focus:border-primary-500 block w-24 rounded-md sm:text-sm border-gray-200" - /> - </span> - <select - value={table.getState().pagination.pageSize} - className="focus:ring-primary-500 focus:border-primary-500 block rounded-md sm:text-sm border-gray-200" - onChange={(e) => { - table.setPageSize(Number(e.target.value)); - }} - > - {[20, 50, 100, 250, 500, 10000, table.getCoreRowModel().rows.length].map((pageSize) => - pageSize <= table.getCoreRowModel().rows.length ? ( - <option key={pageSize} value={pageSize}> - {pageSize === table.getCoreRowModel().rows.length - ? `Show all (${table.getCoreRowModel().rows.length})` - : `Show ${pageSize}`} - </option> - ) : null, - )} - </select> - </div> - <br /> - <div> - {Object.keys(rowSelection).length} of {table.getPreFilteredRowModel().rows.length} Total Rows Selected - </div> - <hr /> - <br /> - <div> - <button className="border rounded p-2 mb-2" onClick={() => console.info('rowSelection', rowSelection)}> - Log `rowSelection` state - </button> - </div> - <div> - <button - className="border rounded p-2 mb-2" - onClick={() => console.info('table.getSelectedRowModel().flatRows', table.getSelectedRowModel().flatRows)} - > - Log table.getSelectedRowModel().flatRows - </button> + ))} + </thead> + <tbody className=""> + {table.getRowModel().rows.map((row, rowIndex) => { + return ( + <tr + key={row.id} + role="row" + className={ + rowIndex % 2 === 0 + ? 'bg-white group border-l-4 border-transparent transition hover:border-primary-600' + : 'bg-gray-50 group border-l-4 border-transparent transition hover:border-primary-600' + } + > + {row.getVisibleCells().map((cell) => { + return ( + <td + key={cell.id} + className={ + cell.id.substring(2) === 'select' + ? 'w-4 px-6 py-4 whitespace-nowrap text-sm text-gray-500' + : 'px-6 py-4 whitespace-nowrap text-sm text-gray-500' + } + > + {flexRender(cell.column.columnDef.cell, cell.getContext())} + </td> + ); + })} + </tr> + ); + })} + </tbody> + </table> + <hr /> + <br /> + <div> + <button className="border rounded p-2 mb-2" onClick={() => console.info('rowSelection', rowSelection)}> + Log `rowSelection` state + </button> + </div> + <div> + <button + className="border rounded p-2 mb-2" + onClick={() => console.info('table.getSelectedRowModel().flatRows', table.getSelectedRowModel().flatRows)} + > + Log table.getSelectedRowModel().flatRows + </button> + </div> </div> </> ); @@ -474,45 +433,7 @@ export const Users: React.FC = () => { )} </div> - <div className="flex justify-between w-100 my-3 items-center mb-5 "> - <div className="flex items-center"> - <div className="inline-block"> - <label htmlFor="email" className="block text-sm font-medium text-gray-700 sr-only"> - Search candidates - </label> - <div className="mt-1 flex rounded-md shadow-sm"> - <div className="relative flex items-stretch flex-grow focus-within:z-10"> - <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> - <SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" /> - </div> - <input - type="text" - name="email" - id="email" - className="focus:ring-primary-500 focus:border-primary-500 block w-full rounded-md pl-10 sm:text-sm border-gray-200" - placeholder="Search Users" - onChange={debouncedSearch} - /> - </div> - </div> - </div> - </div> - - {selectedRowsIds && Object.keys(selectedRowsIds).length !== 0 && ( - <div className="flex items-center"> - <button - onClick={() => {}} - type="button" - className="inline-flex items-center px-4 py-2 text-sm font-medium rounded-md text-red-700 bg-red-50 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" - > - <TrashIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" /> - Delete - </button> - </div> - )} - </div> - - <div className="flex flex-col"> + {/* <div className="flex flex-col"> <div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> <div className="shadow border-b border-gray-200 sm:rounded-lg overflow-hidden"> @@ -525,13 +446,11 @@ export const Users: React.FC = () => { </div> </div> </div> - </div> + </div> */} <div className="flex flex-col"> <div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> - <div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> - <div className="shadow border-b border-gray-200 sm:rounded-lg overflow-hidden">{CreateUserTable()}</div> - </div> + <div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">{CreateUserTable()}</div> </div> </div>