Skip to content
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 Admin UI (#30) #54

Merged
merged 3 commits into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
"@testing-library/jest-dom": "^4.2.4",
"@trussworks/react-uswds": "^1.9.1",
"http-proxy-middleware": "^1.0.5",
"lodash": "^4.17.20",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-router-prop-types": "^1.0.5",
"react-scripts": "3.4.1",
"uswds": "^2.8.1"
},
Expand Down Expand Up @@ -85,9 +87,10 @@
]
},
"devDependencies": {
"@testing-library/dom": "^7.21.7",
"@sheerun/mutationobserver-shim": "^0.3.3",
"@testing-library/dom": "^7.24.2",
"@testing-library/react": "^10.4.9",
"@testing-library/user-event": "^7.1.2",
"@testing-library/user-event": "^12.1.5",
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.2.0",
Expand All @@ -97,6 +100,7 @@
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-react": "^7.20.5",
"eslint-plugin-react-hooks": "^4.0.8",
"history": "^5.0.0",
"jest-junit": "^11.1.0"
}
}
7 changes: 2 additions & 5 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { Alert } from '@trussworks/react-uswds';

import Header from './components/Header';
import Admin from './pages/Admin';

function App() {
return (
Expand All @@ -21,11 +22,7 @@ function App() {
World!
</Alert>
</Route>
<Route path="/second">
<div>
Hello second Page!
</div>
</Route>
<Route path="/admin/:userId?" component={Admin} />
</Switch>
</BrowserRouter>
);
Expand Down
89 changes: 89 additions & 0 deletions frontend/src/Constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
export const REGIONAL_SCOPES = [
{
name: 'READ_WRITE_REPORTS',
description: 'Can view and create/edit reports in the region',
},
{
name: 'READ_REPORTS',
description: 'Can view reports in the region',
},
{
name: 'THIRD_SCOPE',
description: 'A third scope used as an example',
},
{
name: 'FORTH_SCOPE',
description: 'Another testing scope that will soon be deleted',
},
];

export const GLOBAL_SCOPES = [
{
name: 'SITE_ACCESS',
description: 'User can login and view the TTAHUB site',
},
{
name: 'ADMIN',
description: 'User can view the admin panel and change user permissions (including their own)',
},
];

export const JOB_TITLES = [
'Program Specialist',
'Early Childhood Specialist',
'Grantee Specialist',
'Family Engagement Specialist',
'Health Specialist',
'Systems Specialist',
];

export const REGIONS = [
{
number: 1,
name: 'Boston',
},
{
number: 2,
name: 'New York City',
},
{
number: 3,
name: 'Philadelphia',
},
{
number: 4,
name: 'Atlanta',
},
{
number: 5,
name: 'Chicago',
},
{
number: 6,
name: 'Dallas',
},
{
number: 7,
name: 'Kansas City',
},
{
number: 8,
name: 'Denver',
},
{
number: 9,
name: 'San Francisco',
},
{
number: 10,
name: 'Seattle',
},
{
number: 11,
name: 'AIAN',
},
{
number: 12,
name: 'MSHS',
},
];
4 changes: 2 additions & 2 deletions frontend/src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ function Header() {
<NavLink exact to="/">
Home
</NavLink>,
<NavLink to="/second">
Second Page
<NavLink to="/admin">
Admin
</NavLink>,
];

Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/IndeterminateCheckbox.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.usa-checkbox__input:indeterminate+.usa-checkbox__label::before {
background-image: url(../images/minus.svg);
background-repeat: no-repeat;
background-position: center center;
background-size: .75rem auto;
background-color: #949494;
box-shadow: 0 0 0 2px #949494;
}
51 changes: 51 additions & 0 deletions frontend/src/components/IndeterminateCheckbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useEffect, useRef } from 'react';
rahearn marked this conversation as resolved.
Show resolved Hide resolved
import PropTypes from 'prop-types';
import './IndeterminateCheckbox.css';

function Checkbox({
id, name, label, checked, indeterminate, disabled, onChange,
}) {
const indeterminateRef = useRef();
useEffect(() => {
indeterminateRef.current.indeterminate = indeterminate;
});

const onLocalChange = (e) => {
onChange(e, indeterminate);
};

return (
<>
<input
disabled={disabled}
className="usa-checkbox__input"
id={id}
type="checkbox"
name={name}
onChange={onLocalChange}
checked={checked}
ref={indeterminateRef}
/>
<label className="usa-checkbox__label" htmlFor={id}>
{label}
</label>
</>
);
}

Checkbox.propTypes = {
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
label: PropTypes.node.isRequired,
checked: PropTypes.bool.isRequired,
indeterminate: PropTypes.bool,
disabled: PropTypes.bool,
onChange: PropTypes.func.isRequired,
};

Checkbox.defaultProps = {
indeterminate: false,
disabled: false,
};

export default Checkbox;
36 changes: 36 additions & 0 deletions frontend/src/components/JobTitleDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Label, Dropdown,
} from '@trussworks/react-uswds';

import { JOB_TITLES } from '../Constants';

function JobTitleDropdown({
id, name, value, onChange,
}) {
return (
<>
<Label htmlFor={id}>Job Title</Label>
<Dropdown id={id} name={name} value={value} onChange={onChange}>
<option name="default" disabled hidden value="default">Select a Job Title...</option>
{JOB_TITLES.map((jobTitle) => (
<option key={jobTitle} value={jobTitle}>{jobTitle}</option>
))}
</Dropdown>
</>
);
}

JobTitleDropdown.propTypes = {
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
};

JobTitleDropdown.defaultProps = {
value: 'default',
};

export default JobTitleDropdown;
40 changes: 40 additions & 0 deletions frontend/src/components/RegionDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Label, Dropdown,
} from '@trussworks/react-uswds';

import { REGIONS } from '../Constants';

function RegionDropdown({
id, name, value, onChange, includeCentralOffice,
}) {
return (
<>
<Label htmlFor={id}>Region</Label>
<Dropdown id={id} name={name} value={value} onChange={onChange}>
<option name="default" disabled hidden value="default">Select a region...</option>
{REGIONS.map(({ number, name: description }) => (
<option key={number} value={number}>{`${number} - ${description}`}</option>
))}
{includeCentralOffice
&& <option name="central-office" value="co">Central Office</option>}
</Dropdown>
</>
);
}

RegionDropdown.propTypes = {
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
includeCentralOffice: PropTypes.bool,
};

RegionDropdown.defaultProps = {
value: 'default',
includeCentralOffice: false,
};

export default RegionDropdown;
33 changes: 33 additions & 0 deletions frontend/src/components/__tests__/IndeterminateCheckbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import '@testing-library/jest-dom';
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';

import IndeterminateCheckbox from '../IndeterminateCheckbox';

describe('IndeterminateCheckbox', () => {
test('indeterminate can be false', () => {
render(<IndeterminateCheckbox label="false" id="id" name="name" checked indeterminate={false} onChange={() => {}} />);
expect(screen.getByLabelText('false').indeterminate).toBeFalsy();
});

test('indeterminate can be true', () => {
render(<IndeterminateCheckbox label="true" id="id" name="name" checked indeterminate onChange={() => {}} />);
expect(screen.getByLabelText('true').indeterminate).toBeTruthy();
});

test('can be disabled', () => {
render(<IndeterminateCheckbox label="checkbox" id="id" name="name" checked={false} indeterminate={false} disabled onChange={() => {}} />);
expect(screen.getByLabelText('checkbox')).toBeDisabled();
});

test('onChange includes indeterminate', () => {
let result = false;
const onChange = (e, indeterminate) => {
result = indeterminate;
};
render(<IndeterminateCheckbox label="checkbox" id="id" name="name" checked={false} indeterminate onChange={onChange} />);
const checkbox = screen.getByLabelText('checkbox');
fireEvent.click(checkbox);
expect(result).toBeTruthy();
});
});
18 changes: 18 additions & 0 deletions frontend/src/components/__tests__/JobTitleDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import '@testing-library/jest-dom';
import React from 'react';
import { render, screen } from '@testing-library/react';

import JobTitleDropdown from '../JobTitleDropdown';

describe('JobTitleDropdown', () => {
test('shows "select a job title" (the default) when no value is selected', () => {
render(<JobTitleDropdown id="id" name="name" onChange={() => {}} />);
expect(screen.getByLabelText('Job Title').value).toBe('default');
});

test('default option is not selectable', () => {
render(<JobTitleDropdown id="id" name="name" onChange={() => {}} />);
const item = screen.getByLabelText('Job Title').options.namedItem('default');
expect(item.hidden).toBeTruthy();
});
});
28 changes: 28 additions & 0 deletions frontend/src/components/__tests__/RegionDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import '@testing-library/jest-dom';
import React from 'react';
import { render, screen } from '@testing-library/react';

import RegionDropdown from '../RegionDropdown';

describe('RegionalDropdown', () => {
test('shows "select an option" (the default) when no value is selected', () => {
render(<RegionDropdown id="id" name="name" onChange={() => {}} />);
expect(screen.getByLabelText('Region').value).toBe('default');
});

test('default option is not selectable', () => {
render(<RegionDropdown id="id" name="name" onChange={() => {}} />);
const item = screen.getByLabelText('Region').options.namedItem('default');
expect(item.hidden).toBeTruthy();
});

test('does not show central office when includeCentralOffice is not specified', () => {
render(<RegionDropdown id="id" name="name" onChange={() => {}} />);
expect(screen.queryByText('Central Office')).toBeNull();
});

test('with prop includeCentralOffice has central office as an option', () => {
render(<RegionDropdown id="id" name="name" includeCentralOffice onChange={() => {}} />);
expect(screen.queryByText('Central Office')).toBeVisible();
});
});
1 change: 1 addition & 0 deletions frontend/src/images/minus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading