Skip to content

Commit

Permalink
Merge pull request #54 from adhocteam/main
Browse files Browse the repository at this point in the history
Add Admin UI (#30)
  • Loading branch information
rahearn authored Sep 24, 2020
2 parents b8449d5 + bdf91be commit 9a9e79c
Show file tree
Hide file tree
Showing 30 changed files with 1,369 additions and 32 deletions.
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';
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

0 comments on commit 9a9e79c

Please sign in to comment.