react-app/src/components/users/UsersTable.tsx
import React, { useContext, useMemo } from 'react';
import { FilterSearchDataGrid } from '@/components/table/DataTable';
import { Box, SxProps, useTheme, ListSubheader, MenuItem } from '@mui/material';
import { GridColDef, GridComparatorFn, GridEventListener } from '@mui/x-data-grid';
import { MutableRefObject, PropsWithChildren, useEffect, useState } from 'react';
import { useSSO } from '@bcgov/citz-imb-sso-react';
import { IUser } from '@/interfaces/IUser';
import { dateFormatter, statusChipFormatter } from '@/utilities/formatters';
import { GridApiCommunity } from '@mui/x-data-grid/internals';
import { Agency } from '@/hooks/api/useAgencyApi';
import { User } from '@/hooks/api/useUsersApi';
import { LookupContext } from '@/contexts/lookupContext';
import { Role } from '@/constants/roles';
import { getProvider } from '@/utilities/helperFunctions';
const CustomMenuItem = (props: PropsWithChildren & { value: string }) => {
const theme = useTheme();
return (
<MenuItem
sx={{
fontSize: theme.typography.fontSize,
fontWeight: theme.typography.fontWeightMedium,
height: '2.3em',
}}
{...props}
>
{props.children}
</MenuItem>
);
};
const CustomListSubheader = (props: PropsWithChildren) => {
const theme = useTheme();
return (
<ListSubheader
sx={{
fontSize: theme.typography.fontSize,
fontWeight: theme.typography.fontWeightBold,
color: 'rgba(0, 0, 0, 1)',
height: '2.3em',
marginBottom: '5px',
}}
{...props}
>
{props.children}
</ListSubheader>
);
};
interface IUsersTable {
rowClickHandler: GridEventListener<'rowClick'>;
data: Record<string, any>;
isLoading: boolean;
refreshData: () => void;
error: unknown;
}
const statusComparitor: GridComparatorFn = (v1, v2) => {
const statusOrder = ['OnHold', 'Active', 'Disabled', 'Denied'];
const indx1 = statusOrder.indexOf(v1);
const indx2 = statusOrder.indexOf(v2);
if (indx1 < 0 || indx2 < 0) return 0;
else return indx2 - indx1;
};
const UsersTable = (props: IUsersTable) => {
// States and contexts
const { refreshData, data, error, isLoading, rowClickHandler } = props;
const [users, setUsers] = useState([]);
const { state } = useSSO();
const lookup = useContext(LookupContext);
useEffect(() => {
if (error) {
console.error(error);
}
if (data) {
setUsers(data as IUser[]);
} else {
refreshData();
}
}, [state, data]);
const agenciesForSingleSelect = useMemo(() => {
if (lookup.data) {
return lookup.data.Agencies.map((a) => a.Name);
} else {
return [];
}
}, [lookup]);
const rolesForSingleSelect = useMemo(() => {
if (lookup.data) {
return lookup.data.Roles.map((a) => a.Name);
} else {
return [];
}
}, [lookup.data]);
// Sets the preset filter based on the select input
const selectPresetFilter = (value: string, ref: MutableRefObject<GridApiCommunity>) => {
// Clear the quick search contents
switch (value) {
case 'All Users':
ref.current.setFilterModel({ items: [] });
break;
// All Status filters
case 'Active':
case 'OnHold':
case 'Disabled':
case 'Denied':
ref.current.setFilterModel({
items: [
{
value,
operator: 'is',
field: 'Status',
},
],
});
break;
// All Role filters
case 'General User':
case 'Administrator':
case 'Auditor':
case 'No Role':
ref.current.setFilterModel({
items: [
{
value,
operator: 'is',
field: 'Role',
},
],
});
break;
}
};
// Defines the columns used in the table.
const columns: GridColDef[] = [
{
field: 'FirstName',
headerName: 'First Name',
flex: 1,
minWidth: 125,
maxWidth: 200,
},
{
field: 'LastName',
headerName: 'Last Name',
flex: 1,
minWidth: 125,
maxWidth: 200,
},
{
field: 'Status',
headerName: 'Status',
type: 'singleSelect',
valueOptions: ['Active', 'Denied', 'OnHold', 'Disabled'],
renderCell: (params) => {
if (!params.value) return <></>;
return statusChipFormatter(params.value);
},
sortComparator: statusComparitor,
width: 150,
},
{
field: 'Email',
headerName: 'Email Address',
minWidth: 200,
flex: 1,
},
{
field: 'Username',
headerName: 'Provider',
width: 125,
valueGetter: (value) => getProvider(value, lookup?.data?.Config.bcscIdentifier),
},
{
field: 'Agency',
headerName: 'Agency',
minWidth: 125,
flex: 1,
type: 'singleSelect',
valueOptions: agenciesForSingleSelect,
valueGetter: (value?: Agency) => value?.Name ?? ``,
},
{
field: 'Position',
headerName: 'Position',
minWidth: 150,
flex: 1,
},
{
field: 'Role',
headerName: 'Role',
minWidth: 150,
flex: 1,
valueGetter: (value?: Role) => value?.Name ?? `No Role`,
type: 'singleSelect',
valueOptions: [...rolesForSingleSelect, 'No Role'],
},
{
field: 'CreatedOn',
headerName: 'Created On',
minWidth: 120,
valueFormatter: (value) => dateFormatter(value),
type: 'date',
},
{
field: 'LastLogin',
headerName: 'Last Login',
minWidth: 120,
valueFormatter: (value) => dateFormatter(value),
type: 'date',
},
];
const excelDataMap = (data: User[]) => {
return data.map((user) => {
return {
Username: user.Username,
'First Name': user.FirstName,
'Last Name': user.LastName,
Email: user.Email,
Status: user.Status,
Agency: user.Agency?.Name,
'Last Login': user.LastLogin,
Role: user.Role?.Name,
'Created On': user.CreatedOn,
Position: user.Position,
};
});
};
return (
<Box
sx={
{
padding: '24px',
height: 'fit-content',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
} as SxProps
}
>
<Box sx={{ height: 'calc(100vh - 180px)' }}>
<FilterSearchDataGrid
name="users"
onRowClick={rowClickHandler}
tableOperationMode="client"
defaultFilter="All Users"
tableHeader="Users Overview"
excelTitle="Users Table"
customExcelMap={excelDataMap}
addTooltip="Adding a new user from this table is not supported yet. Please advise users to use the access request form."
getRowId={(row) => row.Id}
columns={columns}
rows={users}
loading={isLoading}
// initialState={{
// sorting: {
// sortModel: [{ field: 'Status', sort: 'desc' }],
// },
// }}
onPresetFilterChange={selectPresetFilter}
presetFilterSelectOptions={[
<CustomMenuItem key={'All Users'} value={'All Users'}>
All Users
</CustomMenuItem>,
<CustomListSubheader key={'Status'}>Status</CustomListSubheader>,
<CustomMenuItem key={'Active'} value={'Active'}>
Active
</CustomMenuItem>,
<CustomMenuItem key={'On Hold'} value={'OnHold'}>
On Hold
</CustomMenuItem>,
<CustomMenuItem key={'Disabled'} value={'Disabled'}>
Disabled
</CustomMenuItem>,
<CustomMenuItem key={'Denied'} value={'Denied'}>
Denied
</CustomMenuItem>,
<CustomListSubheader key={'Role'}>Role</CustomListSubheader>,
<CustomMenuItem key={'General User'} value={'General User'}>
General User
</CustomMenuItem>,
<CustomMenuItem key={'Administrator'} value={'Administrator'}>
Administrator
</CustomMenuItem>,
<CustomMenuItem key={'Auditor'} value={'Auditor'}>
Auditor
</CustomMenuItem>,
<CustomMenuItem key={'No Role'} value={'No Role'}>
No Role
</CustomMenuItem>,
]}
/>
</Box>
</Box>
);
};
export default UsersTable;