Skip to content

Commit

Permalink
PIMS-668 Users Table (#2148)
Browse files Browse the repository at this point in the history
  • Loading branch information
dbarkowsky authored and TaylorFries committed Feb 13, 2024
1 parent 5baad42 commit 88fb798
Show file tree
Hide file tree
Showing 11 changed files with 610 additions and 4 deletions.
2 changes: 1 addition & 1 deletion react-app/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ module.exports = {
'no-prototype-builtins': 'off',
'react/prop-types': 'off',
'react/display-name': 'off',
'no-console': 'error',
'no-console': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-inferrable-types': 'off', // ie. const val: number = 4;
'@typescript-eslint/no-empty-function': 'off', // ie. {}
Expand Down
4 changes: 3 additions & 1 deletion react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
},
"dependencies": {
"@bcgov/bc-sans": "2.1.0",
"@bcgov/citz-imb-kc-react": "https://github.com/bcgov/citz-imb-kc-react/releases/download/v1.1.0/bcgov-citz-imb-kc-react-1.1.0.tgz",
"@bcgov/citz-imb-kc-react": "https://github.com/bcgov/citz-imb-kc-react/releases/download/v1.4.0/bcgov-citz-imb-kc-react-1.4.0.tgz",
"@emotion/react": "11.11.3",
"@emotion/styled": "11.11.0",
"@mui/icons-material": "5.15.6",
"@mdi/js": "7.4.47",
"@mdi/react": "1.6.1",
"@mui/material": "5.15.6",
"@mui/x-data-grid": "6.19.2",
"node-xlsx": "0.23.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "7.50.1",
Expand Down
13 changes: 13 additions & 0 deletions react-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ConfigContextProvider } from './contexts/configContext';
import AuthContextProvider from './contexts/authContext';
import AuthRouteGuard from './guards/AuthRouteGuard';
import BaseLayout from './components/layout/BaseLayout';
import UsersTable from '@/pages/UsersTable';

const Router = () => {
return (
Expand All @@ -26,6 +27,18 @@ const Router = () => {
</BaseLayout>
}
/>
<Route path="admin">
<Route
path="users"
element={
<BaseLayout>
<AuthRouteGuard>
<UsersTable />
</AuthRouteGuard>
</BaseLayout>
}
/>
</Route>
</Routes>
</AuthContextProvider>
</ConfigContextProvider>
Expand Down
2 changes: 1 addition & 1 deletion react-app/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const Header: React.FC = () => {
<Link underline="none" href="#" variant="h5">
Disposal Inventory
</Link>
<Link underline="none" href="#" variant="h5">
<Link underline="none" href="/admin/users" variant="h5">
Users
</Link>
</>
Expand Down
112 changes: 112 additions & 0 deletions react-app/src/components/table/KeywordSearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { SxProps, TextField, InputAdornment, useTheme } from '@mui/material';
import React, { Dispatch, SetStateAction, useState } from 'react';
import SearchIcon from '@mui/icons-material/Search';
import CloseIcon from '@mui/icons-material/Close';

/**
* @interface
* @description Props for the KeywordSearch field.
* @prop {Function} onChange (Optional) Function to run when value of field changes.
* @prop {[string, Dispatch<SetStateAction<string>>]} optionalExternalState (Optional) An external state getter and setter for field value. Component also contains internal state if that suffices.
*/
interface IKeywordSearchProps {
onChange?: Function;
optionalExternalState?: [string, Dispatch<SetStateAction<string>>];
}

/**
* @description Input field that is a search icon when minimized but can be expanded upon click.
* @param props Properties passed to the component.
*/
const KeywordSearch = (props: IKeywordSearchProps) => {
const { onChange, optionalExternalState } = props;
const [isOpen, setIsOpen] = useState<boolean>(false);
const [fieldContents, setFieldContents] = optionalExternalState
? optionalExternalState
: useState<string>('');
const theme = useTheme();

// Style shared when both open and closed
const commonStyle: SxProps = {
fontSize: theme.typography.fontWeightBold,
fontFamily: theme.typography.fontFamily,
padding: '5px',
marginBottom: '1px',
boxSizing: 'content-box',
borderRadius: '5px',
};

// Style when open
const openStyle: SxProps = {
...commonStyle,
width: '240px',
transition: 'width 0.3s ease-in, border 1s',
border: `1.5px solid ${theme.palette.grey[400]}`,
'&:focus-within': {
border: '1.5px solid black',
},
};

// Style when closed
const closedStyle: SxProps = {
...commonStyle,
width: '32px',
transition: 'width 0.3s ease-in, border 1s',
border: '1.5px solid transparent',
'&:hover': {
cursor: 'default',
},
};
return (
<TextField
id="keyword-search"
variant="standard"
sx={isOpen ? openStyle : closedStyle}
size="small"
style={{ fontSize: '5em' }}
placeholder="Search..."
value={fieldContents}
onChange={(e) => {
setFieldContents(e.target.value);
if (onChange) onChange(e.target.value);
}}
InputProps={{
disableUnderline: true,
sx: { cursor: 'default' },
endAdornment: (
<InputAdornment position={'end'}>
{fieldContents ? (
<CloseIcon
onClick={() => {
// Clear text and filter
setFieldContents('');
if (onChange) onChange('');
document.getElementById('keyword-search').focus();
}}
sx={{
'&:hover': {
cursor: 'pointer',
},
}}
/>
) : (
<SearchIcon
onClick={() => {
setIsOpen(!isOpen);
document.getElementById('keyword-search').focus();
}}
sx={{
'&:hover': {
cursor: 'pointer',
},
}}
/>
)}
</InputAdornment>
),
}}
/>
);
};

export default KeywordSearch;
6 changes: 6 additions & 0 deletions react-app/src/hooks/api/useUsersApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ const useUsersApi = (absoluteFetch: IFetch) => {
const { parsedBody } = await absoluteFetch.get(`/users/access/requests`);
return parsedBody;
};

const getAllUsers = async () => {
const { parsedBody } = await absoluteFetch.get('/admin/users');
return parsedBody;
};
return {
getLatestAccessRequest,
getAllUsers,
};
};

Expand Down
2 changes: 1 addition & 1 deletion react-app/src/hooks/useDataLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
*/
const useDataLoader = <AFArgs extends any[], AFResponse = unknown, AFError = unknown>(
dataFetcher: AsyncFunction<AFArgs, AFResponse>,
errorHandler: (error: AFError) => void,
errorHandler: (error: AFError) => void = () => {},
) => {
//We have this little useEffect here to avoid touching state if this hook suddenly gets unmounted.
//React doesn't like it when this happens.
Expand Down
27 changes: 27 additions & 0 deletions react-app/src/interfaces/IUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { UUID } from 'crypto';

/**
* @interface
* @description Defines the user object returned from the API.
*/
export interface IUser {
createdOn: string;
updatedOn: string;
updatedByName: string;
updatedByEmail: string;
id: UUID;
keycloakid: UUID;
username: string;
position: string;
displayName: string;
firstName: string;
middleName: string;
lastName: string;
email: string;
isDisabled: true;
emailVerified: true;
note: string;
lastLogin: string;
agency: string;
roles: string; // TODO: Are Agency and Roles going to be singular or multiple?
}
Loading

0 comments on commit 88fb798

Please sign in to comment.