Skip to content
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
13 changes: 2 additions & 11 deletions app/assets/sass/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@

.app-table__free-row {
color: $nhsuk-secondary-text-colour;
box-shadow: inset 4px 0 nhsuk-colour("yellow");

&, &:hover {
background-color: nhsuk-colour("grey-5");
Expand All @@ -86,17 +87,7 @@

.app-table__break-row,
.app-table__break-row:hover {
//background-color: #F0F0F0;
}

.app-table__free-row--multi-slot>td {
//padding-top: nhsuk-spacing(5);
//padding-bottom: nhsuk-spacing(5);
box-shadow:
inset 0 -2px 0 0 white,
inset 0 -3px 0 0 $nhsuk-border-colour,
inset 0 -4px 0 0 white,
inset 0 -5px 0 0 white
background-color: #FFDEF1;
}

// Utilities
Expand Down
22 changes: 21 additions & 1 deletion app/routes/bookings.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,27 @@ function bookings (router, shared) {
const ctx = bookingsCtx(req, 'day')
const sessions = getSessionsForDate(site, ctx.anchorDate)
const scheduledSessions = sessions.map(({ availability: avail, slots }) => {
const scheduledRows = buildDisplayRows(slots, avail, data)
let breakIndex = 0
const scheduledRows = buildDisplayRows(slots, avail, data).map(row => {
if (row.type === 'break') {
const mapped = {
...row,
changeHref: `/sites/${site.id}/session/${avail.id}/${ctx.anchorDate}/breaks/${breakIndex}/change`,
removeHref: `/sites/${site.id}/session/${avail.id}/${ctx.anchorDate}/breaks/${breakIndex}/remove`
}
breakIndex += 1
return mapped
}

if (row.type === 'free') {
return {
...row,
addBreakHref: `/sites/${site.id}/session/${avail.id}/${ctx.anchorDate}/breaks/add?start=${encodeURIComponent(row.startTime)}&end=${encodeURIComponent(row.endTime)}`
}
}

return row
})
const serviceNames = avail.services
.map(id => (data.services[id] ? data.services[id].name : id))
.join(', ')
Expand Down
321 changes: 321 additions & 0 deletions app/routes/sessions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,103 @@ function sessions (router, shared) {
getSessionBookingsForDate,
moveBookings,
ensureSessionEditDraft,
buildSessionBreakDraft,
finaliseSessionBreakDraft,
renderSessionChangePage,
applySessionEditDraft,
getSessionBreakRows,
getSessionEditInfoHref,
getSessionEditSummaryHref,
parseTime,
parseSubmittedTimeBlocks,
updateSessionEditDraftImpacts,
groupedServices
} = shared

function getBookingsHref (siteId, date) {
return `/sites/${siteId}/bookings?date=${date}`
}

function getSessionBreaksBaseHref (siteId, availId, date) {
return `/sites/${siteId}/session/${availId}/${date}/breaks`
}

function getSessionBreakBackHref (siteId, availId, date, draft) {
const base = getSessionBreaksBaseHref(siteId, availId, date)
if (draft.breakAction === 'change' && Number.isInteger(draft.breakIndex)) return `${base}/${draft.breakIndex}/change`
if (draft.breakAction === 'remove') return getBookingsHref(siteId, date)
return `${base}/add`
}

function getBreakAtIndex (avail, breakIndex) {
return getSessionBreakRows(avail)[breakIndex] || null
}

function renderBreakForm (req, res, avail, options = {}) {
const action = options.action || 'add'
const date = req.params.date
const siteId = req.site.id
const base = getSessionBreaksBaseHref(siteId, avail.id, date)
const formAction = action === 'change' && Number.isInteger(options.breakIndex)
? `${base}/${options.breakIndex}/change`
: `${base}/add`

res.render('sites/session-break-form', {
site: req.site,
sessionLabel: avail.label,
date,
action,
errors: options.errors || [],
startError: options.startError || null,
endError: options.endError || null,
breakError: options.breakError || null,
startValue: options.startValue || '',
endValue: options.endValue || '',
formAction,
backHref: options.backHref || getBookingsHref(siteId, date)
})
}

function renderBreakInputError (req, res, avail, options) {
const errors = []
let startError = null
let endError = null

if (options.startMessage) {
startError = { text: options.startMessage }
errors.push({ text: options.startMessage, href: '#start' })
}

if (options.endMessage) {
endError = { text: options.endMessage }
errors.push({ text: options.endMessage, href: '#end' })
}

if (options.breakMessage) {
errors.push({ text: options.breakMessage, href: '#start' })
}

renderBreakForm(req, res, avail, {
action: options.action,
breakIndex: options.breakIndex,
startValue: options.startValue,
endValue: options.endValue,
startError,
endError,
breakError: options.breakMessage,
errors,
backHref: options.backHref
})
}

function storeSessionBreakDraft (req, avail, operation) {
const draft = buildSessionBreakDraft(avail, req.params.date, operation)
if (draft.breakErrorMessage) return { errorMessage: draft.breakErrorMessage }
finaliseSessionBreakDraft(req.site, avail, req.params.date, draft)
req.session.data.sessionEditDraft = draft
return { draft }
}

router.get('/sites/:siteId/session/:availId/:date', (req, res) => {
const avail = req.site.availability.find(a => a.id === req.params.availId)
if (!avail) return res.status(404).send('Availability not found')
Expand Down Expand Up @@ -131,6 +219,239 @@ function sessions (router, shared) {
res.redirect(getSessionEditSummaryHref(req.site.id, avail.id, req.params.date))
})

router.get('/sites/:siteId/session/:availId/:date/breaks/add', (req, res) => {
const avail = req.site.availability.find(a => a.id === req.params.availId)
if (!avail) return res.status(404).send('Availability not found')

renderBreakForm(req, res, avail, {
action: 'add',
startValue: String(req.query.start || '').trim(),
endValue: String(req.query.end || '').trim(),
backHref: getBookingsHref(req.site.id, req.params.date)
})
})

router.post('/sites/:siteId/session/:availId/:date/breaks/add', (req, res) => {
const avail = req.site.availability.find(a => a.id === req.params.availId)
if (!avail) return res.status(404).send('Availability not found')

const startValue = String(req.body.start || '').trim()
const endValue = String(req.body.end || '').trim()
const start = parseTime(startValue)
const end = parseTime(endValue)

if (!start || !end) {
return renderBreakInputError(req, res, avail, {
action: 'add',
startValue,
endValue,
startMessage: start ? null : 'Enter a valid break start time',
endMessage: end ? null : 'Enter a valid break end time',
backHref: getBookingsHref(req.site.id, req.params.date)
})
}

const { draft, errorMessage } = storeSessionBreakDraft(req, avail, { type: 'add', start, end })
if (errorMessage) {
return renderBreakInputError(req, res, avail, {
action: 'add',
startValue,
endValue,
breakMessage: errorMessage,
backHref: getBookingsHref(req.site.id, req.params.date)
})
}

if ((draft.affectedBookingIds || []).length > 0) {
return res.redirect(`${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/warning`)
}

res.redirect(`${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/check`)
})

router.get('/sites/:siteId/session/:availId/:date/breaks/:breakIndex/change', (req, res) => {
const avail = req.site.availability.find(a => a.id === req.params.availId)
if (!avail) return res.status(404).send('Availability not found')

const breakIndex = parseInt(req.params.breakIndex, 10)
const breakRow = getBreakAtIndex(avail, breakIndex)
if (!breakRow) return res.status(404).send('Break not found')

renderBreakForm(req, res, avail, {
action: 'change',
breakIndex,
startValue: breakRow.start,
endValue: breakRow.end,
backHref: getBookingsHref(req.site.id, req.params.date)
})
})

router.post('/sites/:siteId/session/:availId/:date/breaks/:breakIndex/change', (req, res) => {
const avail = req.site.availability.find(a => a.id === req.params.availId)
if (!avail) return res.status(404).send('Availability not found')

const breakIndex = parseInt(req.params.breakIndex, 10)
const breakRow = getBreakAtIndex(avail, breakIndex)
if (!breakRow) return res.status(404).send('Break not found')

const startValue = String(req.body.start || '').trim()
const endValue = String(req.body.end || '').trim()
const start = parseTime(startValue)
const end = parseTime(endValue)

if (!start || !end) {
return renderBreakInputError(req, res, avail, {
action: 'change',
breakIndex,
startValue,
endValue,
startMessage: start ? null : 'Enter a valid break start time',
endMessage: end ? null : 'Enter a valid break end time',
backHref: getBookingsHref(req.site.id, req.params.date)
})
}

const { draft, errorMessage } = storeSessionBreakDraft(req, avail, {
type: 'change',
breakIndex,
start,
end,
originalBreak: breakRow
})
if (errorMessage) {
return renderBreakInputError(req, res, avail, {
action: 'change',
breakIndex,
startValue,
endValue,
breakMessage: errorMessage,
backHref: getBookingsHref(req.site.id, req.params.date)
})
}

if ((draft.affectedBookingIds || []).length > 0) {
return res.redirect(`${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/warning`)
}

res.redirect(`${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/check`)
})

router.get('/sites/:siteId/session/:availId/:date/breaks/:breakIndex/remove', (req, res) => {
const avail = req.site.availability.find(a => a.id === req.params.availId)
if (!avail) return res.status(404).send('Availability not found')

const breakIndex = parseInt(req.params.breakIndex, 10)
const breakRow = getBreakAtIndex(avail, breakIndex)
if (!breakRow) return res.status(404).send('Break not found')

const { errorMessage } = storeSessionBreakDraft(req, avail, {
type: 'remove',
breakIndex,
start: breakRow.start,
end: breakRow.end,
originalBreak: breakRow
})
if (errorMessage) return res.status(400).send(errorMessage)

res.redirect(`${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/check`)
})

router.get('/sites/:siteId/session/:availId/:date/breaks/warning', (req, res) => {
const draft = req.session.data.sessionEditDraft
if (!draft) return res.redirect(getBookingsHref(req.site.id, req.params.date))

const { site } = req
const date = req.params.date
const base = getSessionBreaksBaseHref(site.id, req.params.availId, date)
const affectedIds = new Set(draft.affectedBookingIds || [])
const allBookings = getSessionBookingsForDate(site, req.params.availId, date)
const affected = allBookings.filter(booking => affectedIds.has(booking.id))
const allServices = req.data.services
const affectedBookingRows = affected
.sort((left, right) => left.time.localeCompare(right.time))
.map(booking => ({
time: booking.time,
name: booking.name,
serviceName: allServices[booking.service] ? allServices[booking.service].name : booking.service
}))

res.render('sites/session-edit-warning', {
site,
changeDescription: draft.changeDescription || 'breaks',
affectedCount: affected.length,
affectedBookingRows,
formAction: `${base}/warning`,
backHref: getSessionBreakBackHref(site.id, req.params.availId, date, draft)
})
})

router.post('/sites/:siteId/session/:availId/:date/breaks/warning', (req, res) => {
const draft = req.session.data.sessionEditDraft
if (!draft) return res.redirect(getBookingsHref(req.site.id, req.params.date))

draft.bookingsChoice = req.body.bookingsChoice || 'keep'
res.redirect(`${getSessionBreaksBaseHref(req.site.id, req.params.availId, req.params.date)}/check`)
})

router.get('/sites/:siteId/session/:availId/:date/breaks/check', (req, res) => {
const avail = req.site.availability.find(a => a.id === req.params.availId)
const draft = req.session.data.sessionEditDraft
if (!avail) return res.status(404).send('Availability not found')
if (!draft) return res.redirect(getBookingsHref(req.site.id, req.params.date))

const backHref = (draft.affectedBookingIds || []).length > 0
? `${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/warning`
: getSessionBreakBackHref(req.site.id, avail.id, req.params.date, draft)

res.render('sites/session-break-check', {
site: req.site,
sessionLabel: avail.label,
date: req.params.date,
draft,
formAction: `${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/check`,
backHref,
changeHref: draft.breakAction === 'change' && Number.isInteger(draft.breakIndex)
? `${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/${draft.breakIndex}/change`
: (draft.breakAction === 'add' ? `${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/add` : null)
})
})

router.post('/sites/:siteId/session/:availId/:date/breaks/check', (req, res) => {
const avail = req.site.availability.find(a => a.id === req.params.availId)
const draft = req.session.data.sessionEditDraft
if (!avail) return res.status(404).send('Availability not found')
if (!draft) return res.redirect(getBookingsHref(req.site.id, req.params.date))

applySessionEditDraft(req, avail, draft)
res.redirect(`${getSessionBreaksBaseHref(req.site.id, avail.id, req.params.date)}/success`)
})

router.get('/sites/:siteId/session/:availId/:date/breaks/success', (req, res) => {
const sd = req.session.data.sessionEditSuccess || {}
res.render('sites/session-break-success', {
site: req.site,
sessionLabel: sd.sessionLabel || 'Session',
date: sd.date || req.params.date,
breakAction: sd.breakAction || 'change',
breakStart: sd.breakStart,
breakEnd: sd.breakEnd,
bookingCount: sd.bookingCount || 0,
bookingsChoice: sd.bookingsChoice || 'keep',
contactableCount: sd.contactableCount || 0,
uncontactableCount: sd.uncontactableCount || 0,
notifyPageHref: `/sites/${req.site.id}/session/${req.params.availId}/${req.params.date}/breaks/not-notified`
})
})

router.get('/sites/:siteId/session/:availId/:date/breaks/not-notified', (req, res) => {
const sd = req.session.data.sessionEditSuccess || {}
res.render('sites/availability-shared-not-notified', {
site: req.site,
people: sd.uncontactablePeople || [],
backHref: `/sites/${req.site.id}/session/${req.params.availId}/${req.params.date}/breaks/success`
})
})

router.post('/sites/:siteId/session/:availId/:date/edit', (req, res) => {
const avail = req.site.availability.find(a => a.id === req.params.availId)
if (!avail) return res.status(404).send('Availability not found')
Expand Down
Loading