generated from ministryofjustice/hmpps-template-typescript
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* CDPS-1085: Create dietary requirements page * CDPS-1085: Add role check to dietary requirements page * CDPS-1085: Linting * CDPS-1085: Fix integration tests * CDPS-1085: Print page * CDPS-1085: Print page role protection * CDPS-1085: Update layout
- Loading branch information
1 parent
48dcff2
commit 388fd63
Showing
22 changed files
with
1,017 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
.hmpps-pagination-view-all { | ||
text-align: right; | ||
margin: 15px 0 7px 0; | ||
} | ||
|
||
.moj-pagination__results { | ||
padding: 5px 0; | ||
} | ||
|
||
.hmpps-paged-list-header { | ||
display: flex; | ||
flex-wrap: wrap; | ||
align-items: flex-end; | ||
justify-content: space-between; | ||
padding-bottom: 30px; | ||
margin-bottom: 30px; | ||
|
||
.moj-pagination { | ||
margin-top: 30px; | ||
margin-left: 0; | ||
margin-right: 0; | ||
} | ||
|
||
p.moj-pagination__results { | ||
margin-left: -5px; | ||
} | ||
} | ||
|
||
.hmpps-paged-list-footer { | ||
display: flex; | ||
flex-wrap: wrap; | ||
align-items: flex-start; | ||
justify-content: space-between; | ||
margin-bottom: 30px; | ||
|
||
&__no-pagination { | ||
justify-content: flex-end; | ||
} | ||
|
||
.moj-pagination { | ||
margin-left: 0; | ||
margin-right: 0; | ||
} | ||
|
||
p.moj-pagination__results { | ||
margin-left: -5px; | ||
} | ||
} | ||
|
||
.hmpps-paged-list-header__with-divider { | ||
border-bottom: 1px solid govuk-colour('dark-grey'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
.print-link { | ||
.hmpps-print-link { | ||
display: inline-block; | ||
margin: 0 0 15px -10px; | ||
position: relative; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
[aria-sort] a, | ||
[aria-sort] a:hover { | ||
background-color: rgba(0, 0, 0, 0); | ||
border-width: 0; | ||
-webkit-box-shadow: 0 0 0 0; | ||
-moz-box-shadow: 0 0 0 0; | ||
box-shadow: 0 0 0 0; | ||
color: #005ea5; | ||
cursor: pointer; | ||
font-family: inherit; | ||
font-size: inherit; | ||
font-weight: inherit; | ||
padding: 0 10px 0 0; | ||
position: relative; | ||
text-align: inherit; | ||
font-size: 1em; | ||
margin: 0; | ||
text-decoration: none; | ||
} | ||
|
||
[aria-sort] a:before { | ||
content: ' ▼'; | ||
position: absolute; | ||
right: -1px; | ||
top: 9px; | ||
font-size: 0.5em; | ||
} | ||
|
||
[aria-sort] a:after { | ||
content: ' ▲'; | ||
position: absolute; | ||
right: -1px; | ||
top: 1px; | ||
font-size: 0.5em; | ||
} | ||
|
||
[aria-sort='descending'] a:before { | ||
display: none; | ||
} | ||
|
||
[aria-sort='descending'] a:after { | ||
content: ' ▼'; | ||
font-size: 0.8em; | ||
position: absolute; | ||
right: -5px; | ||
top: 2px; | ||
} | ||
|
||
[aria-sort='ascending'] a:before { | ||
display: none; | ||
} | ||
|
||
[aria-sort='ascending'] a:after { | ||
content: ' ▲'; | ||
font-size: 0.8em; | ||
position: absolute; | ||
right: -5px; | ||
top: 2px; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { Role } from '../../server/enums/role' | ||
import DietaryRequirementsPage from '../pages/dietaryRequirements' | ||
import Page from '../pages/page' | ||
|
||
context('Currently Out Page', () => { | ||
beforeEach(() => { | ||
cy.task('reset') | ||
cy.setupUserAuth({ roles: [`ROLE_PRISON`, `ROLE_${Role.GlobalSearch}`, `ROLE_${Role.DpsApplicationDeveloper}`] }) | ||
cy.setupComponentsData() | ||
}) | ||
|
||
it('Page is visible for mock data', () => { | ||
cy.signIn({ redirectPath: `/dietary-requirements/LEI` }) | ||
cy.visit(`/dietary-requirements/LEI`) | ||
Page.verifyOnPage(DietaryRequirementsPage) | ||
}) | ||
|
||
it('Displays the prisoner information', () => { | ||
cy.signIn({ redirectPath: `/dietary-requirements/LEI` }) | ||
cy.visit(`/dietary-requirements/LEI`) | ||
const page = Page.verifyOnPage(DietaryRequirementsPage) | ||
page.dietaryRequirements().row(0).nameAndPrisonNumber().should('include.text', 'Richard Smith') | ||
page.dietaryRequirements().row(0).nameAndPrisonNumber().should('include.text', 'G4879UP') | ||
page.dietaryRequirements().row(0).location().should('include.text', 'C-3-010') | ||
page.dietaryRequirements().row(0).dietaryRequirements().medical().should('include.text', 'Nutrient Deficiency') | ||
page.dietaryRequirements().row(0).dietaryRequirements().foodAllergies().should('include.text', 'Egg') | ||
page.dietaryRequirements().row(2).dietaryRequirements().personal().should('include.text', 'Kosher') | ||
}) | ||
|
||
context('Sorting', () => { | ||
it('Defaults to no sorting', () => { | ||
cy.signIn({ redirectPath: `/dietary-requirements/LEI` }) | ||
cy.visit(`/dietary-requirements/LEI`) | ||
const page = Page.verifyOnPage(DietaryRequirementsPage) | ||
page.dietaryRequirements().sorting().nameAndNumber().should('have.attr', 'aria-sort', 'none') | ||
page.dietaryRequirements().sorting().location().should('have.attr', 'aria-sort', 'none') | ||
}) | ||
|
||
it('Allows sorting', () => { | ||
cy.signIn({ redirectPath: `/dietary-requirements/LEI` }) | ||
cy.visit(`/dietary-requirements/LEI`) | ||
const page = Page.verifyOnPage(DietaryRequirementsPage) | ||
page.dietaryRequirements().sorting().nameAndNumber().click() | ||
page.dietaryRequirements().sorting().nameAndNumber().should('have.attr', 'aria-sort', 'ascending') | ||
page.dietaryRequirements().sorting().location().should('have.attr', 'aria-sort', 'none') | ||
page.dietaryRequirements().sorting().nameAndNumber().click() | ||
page.dietaryRequirements().sorting().nameAndNumber().should('have.attr', 'aria-sort', 'descending') | ||
page.dietaryRequirements().sorting().location().click() | ||
page.dietaryRequirements().sorting().nameAndNumber().should('have.attr', 'aria-sort', 'none') | ||
page.dietaryRequirements().sorting().location().should('have.attr', 'aria-sort', 'ascending') | ||
page.dietaryRequirements().sorting().location().click() | ||
page.dietaryRequirements().sorting().location().should('have.attr', 'aria-sort', 'descending') | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import Page from './page' | ||
|
||
export default class DietaryRequirementsPage extends Page { | ||
constructor() { | ||
super(`People with dietary requirements in Prison Name`) | ||
} | ||
|
||
dietaryRequirements = () => ({ | ||
row: (i: number) => ({ | ||
nameAndPrisonNumber: () => cy.get('table tbody tr').eq(i).find('td').eq(0), | ||
location: () => cy.get('table tbody tr').eq(i).find('td').eq(1), | ||
dietaryRequirements: () => ({ | ||
medical: () => cy.get('table tbody tr').eq(i).find('td').eq(2).find('[data-qa="medical-requirements"]'), | ||
foodAllergies: () => cy.get('table tbody tr').eq(i).find('td').eq(2).find('[data-qa="food-allergies"]'), | ||
personal: () => cy.get('table tbody tr').eq(i).find('td').eq(2).find('[data-qa="personal-requirements"]'), | ||
}), | ||
}), | ||
sorting: () => ({ | ||
nameAndNumber: () => cy.get('table thead th').eq(0), | ||
location: () => cy.get('table thead th').eq(1), | ||
}), | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { Request, RequestHandler, Response } from 'express' | ||
import { format } from 'date-fns' | ||
import { DietaryRequirementsQueryParams, generateListMetadata, mapToQueryString } from '../utils/generateListMetadata' | ||
import { userHasRoles } from '../utils/utils' | ||
import { Role } from '../enums/role' | ||
|
||
export default class DietaryRequirementsController { | ||
private content = [ | ||
{ | ||
name: 'Richard Smith', | ||
prisonerNumber: 'G4879UP', | ||
location: 'C-3-010', | ||
dietaryRequirements: { | ||
medical: ['Coeliac (cannot eat gluten)', 'Nutrient Deficiency', 'Other: has to eat a low copper diet'], | ||
foodAllergies: ['Egg', 'Other: broccoli allergy'], | ||
personal: [] as string[], | ||
}, | ||
}, | ||
{ | ||
name: 'George Harrison', | ||
prisonerNumber: 'G6333VK', | ||
location: 'B-1-042', | ||
dietaryRequirements: { | ||
medical: [], | ||
foodAllergies: ['Sesame'], | ||
personal: [], | ||
}, | ||
}, | ||
{ | ||
name: 'Harry Thompson', | ||
prisonerNumber: 'G3101UO', | ||
location: 'F-5-031', | ||
dietaryRequirements: { | ||
medical: [], | ||
foodAllergies: [], | ||
personal: ['Kosher'], | ||
}, | ||
}, | ||
] | ||
|
||
public get(): RequestHandler { | ||
return async (req: Request, res: Response) => { | ||
if (!userHasRoles([Role.DpsApplicationDeveloper], res.locals.user.userRoles)) { | ||
return res.render('notFound', { url: '/' }) | ||
} | ||
|
||
const queryParams: DietaryRequirementsQueryParams = {} | ||
if (req.query.page) queryParams.page = +req.query.page | ||
if (req.query.showAll) queryParams.showAll = Boolean(req.query.showAll) | ||
if (req.query.nameAndNumber) queryParams.nameAndNumber = req.query.nameAndNumber as string | ||
if (req.query.location) queryParams.location = req.query.location as string | ||
|
||
const sortNameQuery = () => { | ||
let direction = 'ASC' | ||
|
||
if (queryParams.nameAndNumber && queryParams.nameAndNumber === 'ASC') { | ||
direction = 'DESC' | ||
} | ||
|
||
return mapToQueryString({ ...queryParams, nameAndNumber: direction, location: null }) | ||
} | ||
|
||
const sortLocationQuery = () => { | ||
let direction = 'ASC' | ||
|
||
if (queryParams.location && queryParams.location === 'ASC') { | ||
direction = 'DESC' | ||
} | ||
|
||
return mapToQueryString({ ...queryParams, location: direction, nameAndNumber: null }) | ||
} | ||
|
||
const sortParamToDirection = (param: string) => { | ||
switch (param) { | ||
case 'ASC': | ||
return 'ascending' | ||
case 'DESC': | ||
return 'descending' | ||
default: | ||
return 'none' | ||
} | ||
} | ||
|
||
const sorting = { | ||
nameAndNumber: { | ||
direction: sortParamToDirection(queryParams.nameAndNumber as string), | ||
url: `/dietary-requirements/${req.params.locationId}?${sortNameQuery()}`, | ||
}, | ||
location: { | ||
direction: sortParamToDirection(queryParams.location as string), | ||
url: `/dietary-requirements/${req.params.locationId}?${sortLocationQuery()}`, | ||
}, | ||
} | ||
|
||
// Remove page as this comes from the API | ||
delete queryParams.page | ||
|
||
const listMetadata = generateListMetadata( | ||
{ | ||
content: this.content, | ||
totalElements: 200, | ||
last: false, | ||
totalPages: 10, | ||
size: 10, | ||
number: 0, | ||
sort: { empty: false, sorted: false, unsorted: true }, | ||
first: false, | ||
numberOfElements: 20, | ||
empty: false, | ||
pageable: { | ||
pageNumber: 5, | ||
pageSize: 20, | ||
sort: { | ||
empty: true, | ||
sorted: false, | ||
unsorted: true, | ||
}, | ||
offset: 0, | ||
unpaged: false, | ||
paged: true, | ||
}, | ||
}, | ||
queryParams, | ||
'result', | ||
[], | ||
'', | ||
true, | ||
) | ||
|
||
return res.render('pages/dietaryRequirements', { | ||
content: this.content, | ||
listMetadata, | ||
sorting, | ||
locationId: req.params.locationId, | ||
}) | ||
} | ||
} | ||
|
||
public printAll(): RequestHandler { | ||
return async (req: Request, res: Response) => { | ||
if (!userHasRoles([Role.DpsApplicationDeveloper], res.locals.user.userRoles)) { | ||
return res.render('notFound', { url: '/' }) | ||
} | ||
|
||
return res.render('pages/printDietaryRequirements', { | ||
date: format(new Date(), 'cccc c MMMM yyyy'), | ||
content: this.content, | ||
locationId: req.params.locationId, | ||
}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { RequestHandler, Router } from 'express' | ||
import { Services } from '../services' | ||
import asyncMiddleware from '../middleware/asyncMiddleware' | ||
import DietaryRequirementsController from '../controllers/dietaryRequirementsController' | ||
|
||
export default function dietaryRequirementsRouter(_services: Services): Router { | ||
const router = Router() | ||
|
||
const get = (path: string | string[], ...handlers: RequestHandler[]) => | ||
router.get( | ||
path, | ||
handlers.map(handler => asyncMiddleware(handler)), | ||
) | ||
|
||
const dietaryRequirementsController = new DietaryRequirementsController() | ||
|
||
get('/:locationId', dietaryRequirementsController.get()) | ||
get('/:locationId/print-all', dietaryRequirementsController.printAll()) | ||
|
||
return router | ||
} |
Oops, something went wrong.