From bbedc682afc3cfacd96cc3404a8e5b34cfca4f8d Mon Sep 17 00:00:00 2001 From: Mat Johnson Date: Tue, 21 Jul 2026 13:44:09 +0100 Subject: [PATCH 1/2] Tiny testable thing --- app/views/index.html | 8 + app/views/sessions/01-set-timings.html | 303 ++++++++++++ app/views/sessions/02-organise-slots.html | 574 ++++++++++++++++++++++ 3 files changed, 885 insertions(+) create mode 100644 app/views/sessions/01-set-timings.html create mode 100644 app/views/sessions/02-organise-slots.html diff --git a/app/views/index.html b/app/views/index.html index 2777a90..77ea7fb 100755 --- a/app/views/index.html +++ b/app/views/index.html @@ -39,6 +39,14 @@


+

July 2026

+

Sessions: first run

+
    +
  1. + Set up session +
  2. +
+

May–June 2026

Clinics: first run

    diff --git a/app/views/sessions/01-set-timings.html b/app/views/sessions/01-set-timings.html new file mode 100644 index 0000000..574ffa0 --- /dev/null +++ b/app/views/sessions/01-set-timings.html @@ -0,0 +1,303 @@ +{% extends 'layout.html' %} + +{% set errorState = "false" %} + +{% set pageName = "Create session: set timings" %} + +{% from "_includes/primary-navigation.html" import primaryNavigation %} +{% block header %} + {{ primaryNavigation("Clinics", serviceName) }} +{% endblock %} + +{% block beforeContent %}{% endblock %} + +{% block content %} + +{% set tickSvg %} + +{% endset %} + +
    +
    + +
    + +
    + +
    +
    +
    + + {% if errorState === "true" %} + {{ errorSummary({ + titleText: "There is a problem", + errorList: [ + { + text: "Start time must be entered, in 24 hour format", + href: "#" + }, + { + text: "End time must be entered, in 24 hour format", + href: "#" + }, + { + text: "End time must be later than start time", + href: "#" + }, + { + text: "Start time must be earlier than end time", + href: "#" + }, + { + text: "Slot length must be entered, in minutes", + href: "#" + } + ] + }) }} + {% endif %} + +

    Set timings

    + +
    + +
    +
    + + Session start time + +
    + Use 24 hour format, for example, 10:00 +
    + {% if errorState === "true" %} + + Error: Start time must be entered, in 24 hour format + + {% endif %} + +
    +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    + + Session end time + +
    + Use 24 hour format, for example, 17:00 +
    + {% if errorState === "true" %} + + Error: End time must be entered, in 24 hour format + + {% endif %} + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + +
    + + {% if errorState === "true" %} + + Error: Slot length must be entered, in minutes + + {% endif %} +
    + + +
    +
    + +
    + Information: +

    Capacity calculator

    +

    X total slots in the + session

    + + +
    + + + +
    +
    +
    +
    + +{% endblock %} + +{% block pageScripts %} + +{% endblock %} \ No newline at end of file diff --git a/app/views/sessions/02-organise-slots.html b/app/views/sessions/02-organise-slots.html new file mode 100644 index 0000000..cd17c58 --- /dev/null +++ b/app/views/sessions/02-organise-slots.html @@ -0,0 +1,574 @@ +{% extends 'layout.html' %} + +{% set pageName = "Create session: organise slots" %} + +{% from "_includes/primary-navigation.html" import primaryNavigation %} +{% block header %} + {{ primaryNavigation("Clinics", serviceName) }} +{% endblock %} + +{% block beforeContent %}{% endblock %} + +{% block content %} + + + +{% set tickSvg %} + +{% endset %} + +
    +
    + +
    + +
    + +
    +
    + +
    +

    Organise slots

    +

    + Session time + {{ data.newSession.startTime.hour | zeroPad }}:{{ data.newSession.startTime.minute | zeroPad }} to {{ data.newSession.endTime.hour | zeroPad }}:{{ data.newSession.endTime.minute | zeroPad }} +

    +
    + +
    + + + + + +

    + Click to select a slot. Press shift and click to select a range. +

    + +
    +
    +
    +
    +
    +
    +

    With selected:

    + +
    + +
    +
    +

    + {{ data.newSession.totalSlots }} slots available +

    +

    + +

    +
    +
    + +
    +
    +
    + +{% endblock %} + +{% block pageScripts %} + +{% endblock %} \ No newline at end of file From 11823241739455926c94eb8d154c20fcca2a0a23 Mon Sep 17 00:00:00 2001 From: Mat Johnson Date: Tue, 21 Jul 2026 14:11:44 +0100 Subject: [PATCH 2/2] Add realism - serverside errorchecking, workflow navigation --- app/routes.js | 60 +++++++++++++ app/views/sessions/01-set-timings.html | 105 +++++++--------------- app/views/sessions/02-organise-slots.html | 4 +- 3 files changed, 93 insertions(+), 76 deletions(-) diff --git a/app/routes.js b/app/routes.js index fb6a4e7..6e79bbf 100644 --- a/app/routes.js +++ b/app/routes.js @@ -3,6 +3,8 @@ const express = require('express') const router = express.Router() +const isWholeNumber = str => /^\d+$/.test(str) + router.get('/september-iteration-2/clickthru/04a-example-search-result', function (req, res) { const query = (req.query['search-params'] || '').trim() const allParticipants = (req.session.data.participants && req.session.data.participants.default) || [] @@ -31,6 +33,64 @@ router.get('/september-iteration-2/clickthru/04a-example-search-result', functio }) }) +router.post('/sessions/02-organise-slots', function (req, res) { + const session = (req.body && req.body.newSession) || {} + const startTime = session.startTime || {} + const endTime = session.endTime || {} + + const startHourStr = (startTime.hour || '').trim() + const startMinuteStr = (startTime.minute || '').trim() + const endHourStr = (endTime.hour || '').trim() + const endMinuteStr = (endTime.minute || '').trim() + const durationStr = (session.duration || '').trim() + + const startHour = parseInt(startHourStr, 10) + const startMinute = parseInt(startMinuteStr, 10) + const endHour = parseInt(endHourStr, 10) + const endMinute = parseInt(endMinuteStr, 10) + const duration = parseInt(durationStr, 10) + + const validStartHour = isWholeNumber(startHourStr) && startHour >= 0 && startHour <= 23 + const validStartMinute = isWholeNumber(startMinuteStr) && startMinute >= 0 && startMinute <= 59 + const validEndHour = isWholeNumber(endHourStr) && endHour >= 0 && endHour <= 23 + const validEndMinute = isWholeNumber(endMinuteStr) && endMinute >= 0 && endMinute <= 59 + + const errors = {} + + if (!validStartHour || !validStartMinute) { + errors.startTime = 'Start time must be entered, in 24 hour format' + } + + if (!validEndHour || !validEndMinute) { + errors.endTime = 'End time must be entered, in 24 hour format' + } + + const validDuration = isWholeNumber(durationStr) && duration > 0 + if (!validDuration) { + errors.duration = 'Slot length must be entered, in minutes, as a whole number' + } + + if (!errors.startTime && !errors.endTime) { + const startTotalMinutes = startHour * 60 + startMinute + const endTotalMinutes = endHour * 60 + endMinute + if (endTotalMinutes <= startTotalMinutes) { + errors.startTime = 'Start time must be earlier than end time' + errors.endTime = 'End time must be later than start time' + } else if (!errors.duration && duration > endTotalMinutes - startTotalMinutes) { + errors.duration = 'Slot length must be shorter than the total session duration' + } + } + + req.session.data.newSession = session + + if (Object.keys(errors).length > 0) { + return res.render('sessions/01-set-timings', { + errors + }) + } + res.redirect('/sessions/02-organise-slots') +}) + router.get('/action/stage/:participantId', function (req, res) { const participants = (req.session.data.participants && req.session.data.participants.default) || [] const participantIndex = participants.findIndex((participant) => participant.participantId === req.params.participantId) diff --git a/app/views/sessions/01-set-timings.html b/app/views/sessions/01-set-timings.html index 574ffa0..04324e9 100644 --- a/app/views/sessions/01-set-timings.html +++ b/app/views/sessions/01-set-timings.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} -{% set errorState = "false" %} +{% set errorState = "true" if errors else "false" %} {% set pageName = "Create session: set timings" %} @@ -58,30 +58,19 @@

    {% if errorState === "true" %} + {% set errorList = [] %} + {% if errors.startTime %} + {% set errorList = errorList.concat([{ text: errors.startTime, href: "#start-time-hour" }]) %} + {% endif %} + {% if errors.endTime %} + {% set errorList = errorList.concat([{ text: errors.endTime, href: "#end-time-hour" }]) %} + {% endif %} + {% if errors.duration %} + {% set errorList = errorList.concat([{ text: errors.duration, href: "#duration" }]) %} + {% endif %} {{ errorSummary({ titleText: "There is a problem", - errorList: [ - { - text: "Start time must be entered, in 24 hour format", - href: "#" - }, - { - text: "End time must be entered, in 24 hour format", - href: "#" - }, - { - text: "End time must be later than start time", - href: "#" - }, - { - text: "Start time must be earlier than end time", - href: "#" - }, - { - text: "Slot length must be entered, in minutes", - href: "#" - } - ] + errorList: errorList }) }} {% endif %} @@ -89,7 +78,7 @@

    Set timings

    -
    +
    Session start time @@ -97,9 +86,9 @@

    Set timings

    Use 24 hour format, for example, 10:00
    - {% if errorState === "true" %} + {% if errors and errors.startTime %} - Error: Start time must be entered, in 24 hour format + Error: {{ errors.startTime }} {% endif %} @@ -109,8 +98,8 @@

    Set timings

    - +
    @@ -119,15 +108,15 @@

    Set timings

    - +
    -
    +
    Session end time @@ -135,9 +124,9 @@

    Set timings

    Use 24 hour format, for example, 17:00
    - {% if errorState === "true" %} + {% if errors and errors.endTime %} - Error: End time must be entered, in 24 hour format + Error: {{ errors.endTime }} {% endif %} @@ -147,8 +136,8 @@

    Set timings

    - +
    @@ -156,26 +145,26 @@

    Set timings

    - +
    -
    +
    - {% if errorState === "true" %} + {% if errors and errors.duration %} - Error: Slot length must be entered, in minutes + Error: {{ errors.duration }} {% endif %}
    - +
    @@ -186,7 +175,6 @@

    Set timings

    X total slots in the session

    -
    @@ -228,8 +216,6 @@

    Set timings

    Number.isInteger(hour) && hour >= 0 && hour <= 23 && Number.isInteger(minute) && minute >= 0 && minute <= 59; - const errorElement = document.querySelector('[data-capacity-calculator-error]'); - const calculateTotalSlots = (startHour, startMinute, endHour, endMinute, duration) => { if ( !validTime(startHour, startMinute) || @@ -261,35 +247,6 @@

    Set timings

    const totalSlots = calculateTotalSlots(startHour, startMinute, endHour, endMinute, duration); totalElement.textContent = String(totalSlots); totalInput.value = String(totalSlots); - - - if (!errorElement) { - return; - } - - if (!validTime(startHour, startMinute) || !validTime(endHour, endMinute)) { - errorElement.textContent = 'Enter start and end times in 24-hour format (HH:MM).'; - errorElement.hidden = false; - return; - } - - if (!Number.isInteger(duration) || duration <= 0) { - errorElement.textContent = 'Enter a valid slot duration in minutes.'; - errorElement.hidden = false; - return; - } - - const startMinutes = startHour * 60 + startMinute; - const endMinutes = endHour * 60 + endMinute; - - if (endMinutes <= startMinutes) { - errorElement.textContent = 'End time must be after start time.'; - errorElement.hidden = false; - return; - } - - errorElement.textContent = ''; - errorElement.hidden = true; }; fields.forEach(field => { diff --git a/app/views/sessions/02-organise-slots.html b/app/views/sessions/02-organise-slots.html index cd17c58..11893fc 100644 --- a/app/views/sessions/02-organise-slots.html +++ b/app/views/sessions/02-organise-slots.html @@ -83,10 +83,10 @@

    1. - + 1 Set timings - +