diff --git a/packages/smb/src/components/pages/users/__tests__/users.test.tsx b/packages/smb/src/components/pages/users/__tests__/users.test.tsx
new file mode 100644
index 0000000000..22af5fa725
--- /dev/null
+++ b/packages/smb/src/components/pages/users/__tests__/users.test.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react'
+import { mount } from 'enzyme'
+import { MockedProvider } from '@apollo/react-testing'
+import { BrowserRouter as Router } from 'react-router-dom'
+import { Users, UsersProps } from '../users'
+
+describe('Users', () => {
+ describe('Users', () => {
+ it('should match a snapshot', () => {
+ const mockProps: UsersProps = {}
+ const wrapper = mount(
+
+
+
+
+ ,
+ )
+ expect(wrapper).toMatchSnapshot()
+ })
+ })
+})
diff --git a/packages/smb/src/components/pages/users/index.ts b/packages/smb/src/components/pages/users/index.ts
new file mode 100644
index 0000000000..7d740a8781
--- /dev/null
+++ b/packages/smb/src/components/pages/users/index.ts
@@ -0,0 +1,2 @@
+import Users from './users'
+export default Users
diff --git a/packages/smb/src/components/pages/users/users.tsx b/packages/smb/src/components/pages/users/users.tsx
new file mode 100644
index 0000000000..1027d1394c
--- /dev/null
+++ b/packages/smb/src/components/pages/users/users.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react'
+import { H3, FlexContainerBasic, FlexContainerResponsive } from '@reapit/elements'
+import UserList from '@/components/ui/user-list'
+
+export type UsersProps = {}
+
+export const Users: React.FC = () => {
+ return (
+
+
+ Users
+
+
+
+ )
+}
+
+export default Users
diff --git a/packages/smb/src/components/ui/user-list/__mocks__/users.ts b/packages/smb/src/components/ui/user-list/__mocks__/users.ts
new file mode 100644
index 0000000000..05131360b2
--- /dev/null
+++ b/packages/smb/src/components/ui/user-list/__mocks__/users.ts
@@ -0,0 +1,81 @@
+import { UsersQueryResponse } from '../user-list'
+
+export const users: UsersQueryResponse = {
+ GetNegotiators: {
+ _embedded: [
+ {
+ id: 'MGL',
+ created: '2014-12-26T12:22:01.0000000Z',
+ modified: '2016-11-29T09:57:32.0000000Z',
+ name: 'Abel Robertson',
+ active: true,
+ officeId: 'NPG',
+ email: 'abel.robertson@reapitestates.net',
+ metadata: {},
+ _eTag: '"10109C0209C684789B72FFC53730E31C"',
+ _links: {
+ self: {
+ href: '/negotiators/MGL',
+ },
+ office: {
+ href: '/offices/NPG',
+ },
+ },
+ },
+ {
+ id: 'RPA',
+ created: '2017-02-07T12:08:53.0000000Z',
+ modified: '2017-02-07T12:09:41.0000000Z',
+ name: 'Accounts User',
+ active: true,
+ officeId: 'MCL',
+ _eTag: '"85A9DB571DB893A5FB734105AFF6B464"',
+ _links: {
+ self: {
+ href: '/negotiators/RPA',
+ },
+ office: {
+ href: '/offices/MCL',
+ },
+ },
+ },
+ {
+ id: 'KLB',
+ created: '2010-11-15T14:53:08.0000000Z',
+ modified: '2017-03-10T00:02:08.0000000Z',
+ name: 'Adele Small',
+ active: true,
+ officeId: 'NGL',
+ email: 'adele.small@reapitestates.net',
+ metadata: {},
+ _eTag: '"B28EE5CFF654DB22BF3247CA028C10C3"',
+ _links: {
+ self: {
+ href: '/negotiators/KLB',
+ },
+ office: {
+ href: '/offices/NGL',
+ },
+ },
+ },
+ ],
+ pageNumber: 1,
+ pageSize: 3,
+ pageCount: 3,
+ totalCount: 3,
+ _links: {
+ self: {
+ href: '/negotiators/?PageNumber=1&PageSize=3',
+ },
+ first: {
+ href: '/negotiators/?PageNumber=1&PageSize=3',
+ },
+ next: {
+ href: '/negotiators/?PageNumber=2&PageSize=3',
+ },
+ last: {
+ href: '/negotiators/?PageNumber=3&PageSize=3',
+ },
+ },
+ },
+}
diff --git a/packages/smb/src/components/ui/user-list/__tests__/user-list.test.tsx b/packages/smb/src/components/ui/user-list/__tests__/user-list.test.tsx
new file mode 100644
index 0000000000..7770a18cae
--- /dev/null
+++ b/packages/smb/src/components/ui/user-list/__tests__/user-list.test.tsx
@@ -0,0 +1,96 @@
+import * as React from 'react'
+import { mount, shallow } from 'enzyme'
+import { MockedProvider } from '@apollo/react-testing'
+import { BrowserRouter as Router } from 'react-router-dom'
+import {
+ UserList,
+ UserListProps,
+ renderUserList,
+ RenderUserListParams,
+ getDataTable,
+ tableHeaders,
+ handleChangePage,
+} from '../user-list'
+import { GetUsers } from '../user-list.graphql'
+import { users } from '../__mocks__/users'
+import { error } from '@/graphql/__mocks__/error'
+import { getMockRouterProps } from '@/core/__mocks__/mock-router'
+
+const mockQueries = {
+ request: {
+ query: GetUsers,
+ variables: { pageSize: 100, pageNumber: 1 },
+ },
+ result: {
+ data: users,
+ },
+}
+
+describe('UserList', () => {
+ describe('UserList', () => {
+ it('should match a snapshot', () => {
+ const mockProps: UserListProps = getMockRouterProps({ params: {}, search: '?page=1' })
+ const wrapper = mount(
+
+
+
+
+ ,
+ )
+ expect(wrapper).toMatchSnapshot()
+ })
+ })
+
+ describe('renderUserList', () => {
+ it('should match snapshot', () => {
+ const mockParams: RenderUserListParams = {
+ loading: true,
+ error: undefined,
+ handleChangePage: jest.fn(),
+ dataTable: [],
+ }
+ const wrapper = shallow({renderUserList(mockParams)}
)
+ expect(wrapper).toMatchSnapshot()
+ })
+
+ it('should match snapshot', () => {
+ const mockParams: RenderUserListParams = {
+ loading: false,
+ error,
+ handleChangePage: jest.fn(),
+ dataTable: [],
+ }
+ const wrapper = shallow({renderUserList(mockParams)}
)
+ expect(wrapper).toMatchSnapshot()
+ })
+
+ it('should match snapshot', () => {
+ const mockParams: RenderUserListParams = {
+ loading: false,
+ error: undefined,
+ handleChangePage: jest.fn(),
+ dataTable: getDataTable(users),
+ }
+ const wrapper = shallow({renderUserList(mockParams)}
)
+ expect(wrapper).toMatchSnapshot()
+ })
+
+ describe('getDataTable', () => {
+ it('should run correctly', () => {
+ const dataTable = getDataTable(users)
+ expect(Array.isArray(dataTable)).toBe(true)
+ expect(dataTable.length).toBe(3)
+ expect(dataTable[0]).toEqual(tableHeaders)
+ })
+ })
+
+ describe('handleChangePage', () => {
+ it('should run correctly', () => {
+ const mockParams = { history: { push: jest.fn() } }
+ const fn = handleChangePage(mockParams)
+ fn(2)
+ expect(mockParams.history.push).toBeCalledWith({ search: 'page=2' })
+ })
+ })
+ })
+})
diff --git a/packages/smb/src/components/ui/user-list/index.ts b/packages/smb/src/components/ui/user-list/index.ts
new file mode 100644
index 0000000000..0ff3ee6ef5
--- /dev/null
+++ b/packages/smb/src/components/ui/user-list/index.ts
@@ -0,0 +1,2 @@
+import UserList from './user-list'
+export default UserList
diff --git a/packages/smb/src/components/ui/user-list/user-list.graphql b/packages/smb/src/components/ui/user-list/user-list.graphql
new file mode 100644
index 0000000000..349c5b1f3f
--- /dev/null
+++ b/packages/smb/src/components/ui/user-list/user-list.graphql
@@ -0,0 +1,21 @@
+#import "../../../graphql/fragments/negotiator.graphql"
+
+query GetUsers(
+ $id: [String!],
+ $name: String,
+ $officeId: [String!],
+ $pageSize: Int,
+ $pageNumber: Int,
+ $sortBy: String
+ ) {
+ GetNegotiators(id: $id, name: $name, officeId: $officeId, pageSize: $pageSize, pageNumber: $pageNumber, sortBy: $sortBy) {
+ _embedded {
+ ...Negotiator
+ }
+ pageNumber
+ pageSize
+ pageCount
+ totalCount
+ _links
+ }
+}
diff --git a/packages/smb/src/components/ui/user-list/user-list.tsx b/packages/smb/src/components/ui/user-list/user-list.tsx
new file mode 100644
index 0000000000..8503d31315
--- /dev/null
+++ b/packages/smb/src/components/ui/user-list/user-list.tsx
@@ -0,0 +1,156 @@
+import * as React from 'react'
+import { useLocation, useHistory } from 'react-router-dom'
+import { useQuery } from '@apollo/react-hooks'
+import { QueryResult } from '@apollo/react-common'
+import { ApolloError } from 'apollo-boost'
+import { Loader, Cell, Alert, Section, Pagination, Spreadsheet, Checkbox } from '@reapit/elements'
+import { getParamsFromPath, stringifyObjectIntoQueryString } from '@/utils/client-url-params'
+import { GetUsers } from './user-list.graphql'
+import {
+ PagedResultNegotiatorModel_,
+ NegotiatorModel,
+} from '@reapit/elements/node_modules/@reapit/foundations-ts-definitions/types'
+import { USERS_PER_PAGE } from '@/constants/paginators'
+
+export const tableHeaders: DataTableRow[] = [
+ { readOnly: true, value: 'Username' },
+ { readOnly: true, value: 'Job Title' },
+ { readOnly: true, value: 'Email Address' },
+ { readOnly: true, value: 'Telephone' },
+ { readOnly: true, value: 'Office' },
+ { readOnly: true, value: 'Status' },
+]
+
+export type DataTableRow = {
+ value?: string | boolean
+ readOnly?: boolean
+}
+
+export type UserListProps = {}
+
+export interface UsersQueryParams {
+ pageSize: number
+ pageNumber: number
+ sortBy?: string
+ id?: string[]
+ officeId?: string[]
+ name?: string
+}
+
+export type UsersQueryResponse = {
+ GetNegotiators?: PagedResultNegotiatorModel_
+}
+
+export type RenderUserListParams = {
+ loading: boolean
+ error?: ApolloError
+ pageNumber?: number
+ pageSize?: number
+ totalCount?: number
+ dataTable: DataTableRow[][]
+ handleChangePage: (page: number) => void
+}
+
+const CustomComponent = ({ cellRenderProps }) => {
+ const {
+ row,
+ col,
+ cell: { value },
+ } = cellRenderProps
+ const checkBoxId = `${row}-${col}`
+ return (
+
+
+
+ IS ACTIVE
+
+
+ )
+}
+
+export function getDataTable(data: UsersQueryResponse): DataTableRow[][] {
+ let dataTable: DataTableRow[][] = [tableHeaders]
+ const users: NegotiatorModel[] = data.GetNegotiators?._embedded || []
+ const dataRows: DataTableRow[][] = users.map((user: NegotiatorModel) => [
+ { value: user.name },
+ { value: user.jobTitle },
+ { value: user.email },
+ { value: user.mobilePhone },
+ { value: user.officeId },
+ { value: user.active, CustomComponent },
+ ])
+ dataTable = [tableHeaders, ...dataRows]
+ return dataTable
+}
+
+export const handleChangePage = ({ history }) => (pageNumber: number) => {
+ const searchParams = stringifyObjectIntoQueryString({
+ page: pageNumber,
+ })
+ history.push({
+ search: searchParams,
+ })
+}
+
+export const renderUserList = ({
+ loading,
+ error,
+ dataTable,
+ pageNumber = 0,
+ pageSize = 0,
+ totalCount = 0,
+ handleChangePage,
+}: RenderUserListParams) => {
+ if (loading) {
+ return
+ }
+ if (error) {
+ return
+ }
+ return (
+
+
+
+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the
+ industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type
+ and scrambled it to make a type specimen book.
+
+
+ }
+ />
+
+
+ )
+}
+
+export const UserList: React.FC = () => {
+ const location = useLocation()
+ const history = useHistory()
+ const params = getParamsFromPath(location?.search)
+ const page = Number(params?.page) || 1
+ const { loading, error, data } = useQuery(GetUsers, {
+ variables: { pageSize: USERS_PER_PAGE, pageNumber: page },
+ fetchPolicy: 'network-only',
+ }) as QueryResult
+ const dataTable = getDataTable(data || { GetNegotiators: { _embedded: [] } })
+ return (
+
+ {renderUserList({
+ loading,
+ error,
+ dataTable,
+ pageNumber: data?.GetNegotiators?.pageNumber,
+ pageSize: data?.GetNegotiators?.pageSize,
+ totalCount: data?.GetNegotiators?.totalCount,
+ handleChangePage: handleChangePage({ history }),
+ })}
+
+ )
+}
+
+export default UserList
diff --git a/packages/smb/src/constants/paginators.ts b/packages/smb/src/constants/paginators.ts
index 19cf7ba2a9..d2c7b85adb 100644
--- a/packages/smb/src/constants/paginators.ts
+++ b/packages/smb/src/constants/paginators.ts
@@ -1 +1,2 @@
export const OFFICES_PER_PAGE = 12
+export const USERS_PER_PAGE = 12
diff --git a/packages/smb/src/core/router.tsx b/packages/smb/src/core/router.tsx
index c28951c893..40b425162a 100644
--- a/packages/smb/src/core/router.tsx
+++ b/packages/smb/src/core/router.tsx
@@ -10,6 +10,7 @@ export const history = createBrowserHistory()
const LoginPage = React.lazy(() => import('../components/pages/login'))
const Home = React.lazy(() => import('../components/pages/home'))
const OfficesPage = React.lazy(() => import('../components/pages/offices'))
+const UsersPage = React.lazy(() => import('../components/pages/users'))
const GetStartedPage = React.lazy(() => import('../components/pages/get-started'))
const Router = () => {
@@ -22,6 +23,7 @@ const Router = () => {
+
diff --git a/packages/smb/src/graphql/fragments/negotiator.graphql b/packages/smb/src/graphql/fragments/negotiator.graphql
new file mode 100644
index 0000000000..686fd84de4
--- /dev/null
+++ b/packages/smb/src/graphql/fragments/negotiator.graphql
@@ -0,0 +1,16 @@
+fragment Negotiator on NegotiatorModel {
+ id
+ created
+ modified
+ name
+ jobTitle
+ active
+ officeId
+ workPhone
+ mobilePhone
+ email
+ metadata
+ _eTag
+ _links
+ _embedded
+}