diff --git a/app/assets/sass/components/_calendar-table.scss b/app/assets/sass/components/_calendar-table.scss index eae3d13..d27448c 100644 --- a/app/assets/sass/components/_calendar-table.scss +++ b/app/assets/sass/components/_calendar-table.scss @@ -57,6 +57,7 @@ .--date-selected .calendar-table-date { background-color: $nhsuk-focus-colour; + outline: 2px solid $nhsuk-input-border-colour; } .--schedule-start { diff --git a/app/assets/sass/components/_session-slots.scss b/app/assets/sass/components/_session-slots.scss index f38879f..1ca0a53 100644 --- a/app/assets/sass/components/_session-slots.scss +++ b/app/assets/sass/components/_session-slots.scss @@ -42,6 +42,7 @@ display: flex; align-items: first baseline; padding-top: nhsuk-px-to-rem(8px); + padding-bottom: nhsuk-px-to-rem(8px); } .slot.is-hold {} diff --git a/app/data/session-data-defaults.js b/app/data/session-data-defaults.js index 40b2268..b7be8bb 100644 --- a/app/data/session-data-defaults.js +++ b/app/data/session-data-defaults.js @@ -77,6 +77,8 @@ module.exports = { duration: '10', bookableSlotCount: 36, nonBookableSlotCount: 6, + standardSlotCount: 0, + heldSlotCount: 36, bookableBreakdown: [ { label: 'Special appointment', @@ -175,18 +177,16 @@ module.exports = { duration: '10', bookableSlotCount: 36, nonBookableSlotCount: 6, + standardSlotCount: 28, + heldSlotCount: 8, bookableBreakdown: [ { - label: 'Held for rebooking', + label: 'Rebooking', count: 4 }, { label: 'Special appointment', count: 4 - }, - { - label: 'Standard', - count: 28 } ], nonBookableBreakdown: [ diff --git a/app/routes/clinic-sessions.js b/app/routes/clinic-sessions.js index a0111f8..f8725d9 100644 --- a/app/routes/clinic-sessions.js +++ b/app/routes/clinic-sessions.js @@ -11,7 +11,7 @@ const { combineSlotsHandler, divideSlotsHandler } = require('./slot-structure-handlers') -const { deleteSessionMapImage, saveSessionMapImage, sessionMapImageExists } = require('../lib/session-map-svg') +const { buildSessionMapSvg, deleteSessionMapImage, saveSessionMapImage, sessionMapImageExists } = require('../lib/session-map-svg') /* /app/views/sessions/ @@ -83,6 +83,20 @@ router.post('/sessions/03-slot-structure', function (req, res) { res.redirect('/sessions/04-check-template') }) +router.get('/sessions/04-check-template', function (req, res) { + if (!req.session.data.createSessionTemplate) { + req.session.data.createSessionTemplate = {} + } + + const { svg, description } = buildSessionMapSvg(buildSessionSlots(req.session.data.createSessionTemplate)) + + // A data URI keeps the preview out of the image directory, so nothing is left to clean up + res.render('sessions/04-check-template', { + mapImageSrc: `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`, + mapImageAlt: description + }) +}) + // Saves the draft as a template, overwriting the template being edited if there is one router.post('/sessions/save-template', function (req, res) { const template = req.session.data.createSessionTemplate || {} @@ -184,6 +198,8 @@ router.post('/sessions/mark-hold-web-rebooking', holdHandler({ reason: 'online b router.post('/sessions/mark-hold-technical-recall', holdHandler({ reason: 'technical recall', ...sessionsSlotStructure })) router.post('/sessions/mark-hold-generic', holdHandler(sessionsSlotStructure)) router.post('/sessions/mark-special-appointment', specialAppointmentHandler(sessionsSlotStructure)) +router.post('/sessions/mark-admin', nonBookableHandler({ reason: 'admin', ...sessionsSlotStructure })) +router.post('/sessions/mark-downtime', nonBookableHandler({ reason: 'downtime', ...sessionsSlotStructure })) router.post('/sessions/mark-non-bookable-generic', nonBookableHandler(sessionsSlotStructure)) router.post('/sessions/combine-slots', combineSlotsHandler(sessionsSlotStructure)) router.post('/sessions/divide-slots', divideSlotsHandler(sessionsSlotStructure)) diff --git a/app/routes/slot-structure-handlers.js b/app/routes/slot-structure-handlers.js index ee468be..49ff93a 100644 --- a/app/routes/slot-structure-handlers.js +++ b/app/routes/slot-structure-handlers.js @@ -56,11 +56,12 @@ const buildSessionSlots = (sessionData) => { const nonBookableSlotCount = sumSpans(slots.filter((slot) => slot.isNonBookable)) sessionData.bookableSlotCount = bookableSlotCount sessionData.nonBookableSlotCount = nonBookableSlotCount + sessionData.heldSlotCount = sumSpans(slots.filter((slot) => (slot.isHold || slot.isSpecialAppointment) && !slot.isNonBookable)) const capitalise = (text) => text.charAt(0).toUpperCase() + text.slice(1) const slotTypeLabel = (slot) => { - if (slot.isNonBookable) return slot.nonBookableReason ? capitalise(slot.nonBookableReason) : 'Non-bookable' - if (slot.isHold) return slot.holdReason ? `Held for ${slot.holdReason}` : 'Held' + if (slot.isNonBookable) return slot.nonBookableReason ? capitalise(slot.nonBookableReason) : 'Other non-bookable' + if (slot.isHold) return slot.holdReason ? `${slot.holdReason}` : 'Other hold' if (slot.isSpecialAppointment) return 'Special appointment' return 'Standard' } @@ -78,12 +79,14 @@ const buildSessionSlots = (sessionData) => { const secondIndex = order.indexOf(second.label) return (firstIndex === -1 ? order.length : firstIndex) - (secondIndex === -1 ? order.length : secondIndex) }) - sessionData.bookableBreakdown = breakdown(slots.filter((slot) => !slot.isNonBookable), [ - 'Held for rebooking', 'Held for online booking', 'Held for technical recall', - 'Special appointment', 'Held', 'Standard' + const bookableItems = breakdown(slots.filter((slot) => !slot.isNonBookable), [ + 'Standard', 'Special appointment', 'Rebooking', 'Online booking', 'Technical recall', 'Held' ]) + const standardItem = bookableItems.find((item) => item.label === 'Standard') + sessionData.standardSlotCount = standardItem ? standardItem.count : 0 + sessionData.bookableBreakdown = bookableItems.filter((item) => item.label !== 'Standard') sessionData.nonBookableBreakdown = breakdown(slots.filter((slot) => slot.isNonBookable), [ - 'Staff break', 'Staff meeting', 'Non-bookable' + 'Staff break', 'Staff meeting', 'Admin', 'Downtime', 'Non-bookable' ]) return { diff --git a/app/views/create-capacity-from-zero/clinic-summary.html b/app/views/create-capacity-from-zero/clinic-summary.html index eebe484..c431d0f 100644 --- a/app/views/create-capacity-from-zero/clinic-summary.html +++ b/app/views/create-capacity-from-zero/clinic-summary.html @@ -84,17 +84,18 @@
-
+

With selected:

+

Availability

  • - +
  • - +
@@ -129,9 +130,9 @@

  • -
  • +
  • @@ -148,13 +149,23 @@

    Important: No current schedule

    -

    - A schedule is a block of confirmed time and capacity (usually agreed as a rota). Each schedule has a start date and an end date, for example a 6 week block. -

    +
    +

    + There are no schedules in this clinic at the moment. Schedules will appear here when you have created them. +

    + + + + Create a schedule + + +
    {% endif %} - Add a new schedule + Create a schedule diff --git a/app/views/create-capacity-from-zero/create-clinic.html b/app/views/create-capacity-from-zero/create-clinic.html index a54461b..c332b6d 100644 --- a/app/views/create-capacity-from-zero/create-clinic.html +++ b/app/views/create-capacity-from-zero/create-clinic.html @@ -39,7 +39,7 @@

    Clinic details

    -
    +
    @@ -51,12 +51,12 @@

    Clinic details

    -
    +
    diff --git a/app/views/create-capacity-from-zero/create-schedule.html b/app/views/create-capacity-from-zero/create-schedule.html index 036e4b9..dcc5745 100644 --- a/app/views/create-capacity-from-zero/create-schedule.html +++ b/app/views/create-capacity-from-zero/create-schedule.html @@ -11,7 +11,7 @@ {% block beforeContent %} - Back to clinic: {{ data.createCapacityFromZero.clinicName }} + Back to clinic {% endblock %} @@ -37,16 +37,15 @@ }) }} {% endif %} -

    Create schedule

    - - +

    + {{ data.createCapacityFromZero.clinicName }} + Create schedule +

    -
    +
    Start date @@ -130,8 +129,9 @@

    Create schedule

    - - +
    + +
    diff --git a/app/views/create-capacity-from-zero/index.html b/app/views/create-capacity-from-zero/index.html index f7595e2..5a7be96 100644 --- a/app/views/create-capacity-from-zero/index.html +++ b/app/views/create-capacity-from-zero/index.html @@ -119,33 +119,32 @@
    -
    + +

    Active clinics

    - {{ button({ - text: "Create a new clinic", - href: "/create-capacity-from-zero/create-clinic", - classes: "nhsuk-button--secondary" - }) }} +
    + {{ button({ + text: "Create new clinic", + href: "/create-capacity-from-zero/create-clinic", + classes: "nhsuk-button--secondary-solid" + }) }} + Manage templates +
    -
    -
    - -
    -
    - - - - @@ -153,32 +152,29 @@

    Active clinics

    -
    + Clinic + Unit and location - Scheduled timespan + + Current end date + Capacity
    - Mereford central ongoing
    - NNN-NNN-systemid-20260501 + Mereford central ongoing
    Mereford and Wensley Screening Centre
    Mereford and Wensley District Hospital
    - 7 Sep 2026 to 27 Nov 2026 + 15 Dec 2026 - 1500 slots available
    - of which 150 held + 1500 slots available
    - Drummond van visits
    - NNN-NNN-systemid-20260501 + Drummond van visits
    Drummond
    East Mereford Community Hub
    - 17 Nov 2026 to 24 Dec 2026 + 24 Dec 2026 No sessions assigned @@ -186,123 +182,23 @@

    Active clinics

    - Pankhurst van visits
    - NNN-NNN-systemid-20261028 + Pankhurst van visits
    Pankhurst
    Riverside Sports Centre
    - 17 Sep 2026 to 27 Oct 2026 + 27 Dec 2026 - {{ padlockSvg | safe }} - 1500 slots
    - + 1500 slots available
    -
    - -
    - - - {% endblock %} diff --git a/app/views/create-capacity-from-zero/select-session-template.html b/app/views/create-capacity-from-zero/select-session-template.html index 703ef8a..e93ecec 100644 --- a/app/views/create-capacity-from-zero/select-session-template.html +++ b/app/views/create-capacity-from-zero/select-session-template.html @@ -32,73 +32,95 @@

    -

    Choose session template

    +

    Select session template

    {# Session templates first (newest first), then the examples; a new grid row wraps every two cards #} {% set allTemplates = (data.sessionTemplates | reverse) %} +{% set realTemplates = [] %} {% for template in allTemplates %} -{% if loop.index0 % 2 == 0 %}
    {% endif %} -
    -
    -
    -
    -

    - {{ template.name }} - {% if templateSaved and loop.first and not template.example %}{{ "Updated" if templateSaved == "updated" else "New" }}{% endif %} -

    - {% if template.note %} -

    {{ template.note }}

    - {% endif %} -
    + {% if not template.example %} + {% set realTemplates = (realTemplates.push(template), realTemplates) %} + {% endif %} +{% endfor %} + +{% if realTemplates | length == 0 %} +
    +
    +

    + + Important: No session templates + +

    +

    + A session represents one “clinic day” of slots. Common slot patterns can be captured in session templates for easy reuse. +

    +
    +
    +{% else %} + {% for template in realTemplates %} + {% if loop.index0 % 2 == 0 %}
    {% endif %} +
    +
    +
    +
    +

    + {{ template.name }} + {% if templateSaved and loop.first and not template.example %}{{ "Updated" if templateSaved == "updated" else "New" }}{% endif %} +

    + {% if template.note %} +

    {{ template.note }}

    + {% endif %} +
    {% if template.slug or template.id %} Select template - {% endif %} -
    -
    -
    - {% if template.imagePath %} - {{ template.imageAlt }} - {% else %} - - {% endif %} -
    -
    -
    -
    -
    - Session times -
    -
    - {{ template.sessionStartLabel }} to {{ template.sessionEndLabel }} -
    -
    -
    -
    - Base slot length -
    -
    - {{ template.duration }} minutes -
    + {% endif %} +
    +
    +
    + {% if template.imagePath %} + {{ template.imageAlt }} + {% else %} + + {% endif %}
    -
    -
    - {{ template.totalLabel or "Total slots" }} -
    -
    - {{ template.totalSlotCount }} -
    -
    -
    +
    +
    +
    +
    + Session times +
    +
    + {{ template.sessionStartLabel }} to {{ template.sessionEndLabel }} +
    +
    +
    +
    + Base slot length +
    +
    + {{ template.duration }} minutes +
    +
    +
    +
    + Total slots +
    +
    + {{ template.totalSlotCount }} +
    +
    +
    +
    +
    -
    -
    -{% if loop.index0 % 2 == 1 or loop.last %}
    {% endif %} -{% endfor %} + {% if loop.index0 % 2 == 1 or loop.last %}
    {% endif %} + {% endfor %} +{% endif %}
    @@ -136,25 +158,33 @@

    {% for slot in template.slots %} -
    - {{ slot.startLabel }} - to {{ slot.endLabel }} - - {% if slot.isNonBookable %} - Non-bookable{% if slot.nonBookableReason %}{{ slot.nonBookableReason }}{% endif %} - {% elseif slot.isSpecialAppointment %} - Special appointment - {% elseif slot.isHold %} - Held{% if slot.holdReason %}{{ slot.holdReason }}{% endif %} - {% endif %} - {% if slot.isCombined %} - {{ slot.span }} slots - {% endif %} - -
    - {% for position in slot.mergedChildren %} - - {% endfor %} +
    + + + + {{ slot.startLabel }} to {{ slot.endLabel }} + {% if slot.isCombined %} +
    {{ slot.span }} slots +
    {{ data.createSessionTemplate.duration * slot.span }} minutes + {% else %} +
    {{ data.createSessionTemplate.duration }} minutes + {% endif %} +
    + + +
    +
    + {% for position in slot.mergedChildren %} + + {% endfor %} {% endfor %}
    diff --git a/app/views/index.html b/app/views/index.html index 5ec79b1..63e3c90 100755 --- a/app/views/index.html +++ b/app/views/index.html @@ -8,7 +8,7 @@ {% block content %} {{ serviceName }}

    - Cohort to clinic prototypes + Cohort to clinic prototypes HELLO

      diff --git a/app/views/sessions/00-template-listing.html b/app/views/sessions/00-template-listing.html index c8f3c9b..9242bd9 100644 --- a/app/views/sessions/00-template-listing.html +++ b/app/views/sessions/00-template-listing.html @@ -8,23 +8,9 @@ {% endblock %} {% block beforeContent %} - {% endblock %} {% block content %} @@ -45,79 +31,111 @@

      {% endif %}
      -

      Session templates

      - Create a new session template +

      Clinic session templates

      + Create a session template
      {# Session templates first (newest first), then the examples; a new grid row wraps every two cards #} {% set allTemplates = (data.sessionTemplates | reverse) %} +{% set realTemplates = [] %} {% for template in allTemplates %} -{% if loop.index0 % 2 == 0 %}
      {% endif %} -
      -
      -
      -
      -

      - {{ template.name }} - {% if templateSaved and loop.first and not template.example %}{{ "Updated" if templateSaved == "updated" else "New" }}{% endif %} -

      - {% if template.note %} -

      {{ template.note }}

      - {% endif %} -
      - {% if template.example %} - - {% elif template.id and template.source %} - - Edit template + {% if not template.example %} + {% set realTemplates = (realTemplates.push(template), realTemplates) %} + {% endif %} +{% endfor %} + +{% if realTemplates | length == 0 %} +
      +
      +

      + + Important: No session templates + +

      +
      -
      -
      - {% if template.imagePath %} - {{ template.imageAlt }} - {% else %} - - {% endif %} -
      -
      -
      -
      -
      - Session times -
      -
      - {{ template.sessionStartLabel }} to {{ template.sessionEndLabel }} -
      +
      +
      +{% else %} + {% for template in realTemplates %} + {% if loop.index0 % 2 == 0 %}
      {% endif %} +
      +
      +
      +
      +

      + {{ template.name }} + {% if templateSaved and loop.first and not template.example %}{{ "Updated" if templateSaved == "updated" else "New" }}{% endif %} +

      + {% if template.note %} +

      {{ template.note }}

      + {% endif %}
      -
      -
      - Base slot length -
      -
      - {{ template.duration }} minutes -
      + {% if template.example %} + + {% elif template.id and template.source %} + + Edit template + + {% endif %} +
      +
      +
      + {% if template.imagePath %} + {{ template.imageAlt }} + {% else %} + + {% endif %}
      -
      -
      - Total slots -
      -
      - {{ template.totalSlotCount }} -
      +
      +
      +
      +
      + Session times +
      +
      + {{ template.sessionStartLabel }} to {{ template.sessionEndLabel }} +
      +
      +
      +
      + Base slot length +
      +
      + {{ template.duration }} minutes +
      +
      +
      +
      + Total slots +
      +
      + {{ template.totalSlotCount }} +
      +
      +
      +
      - - +
      -
      -
      -{% if loop.index0 % 2 == 1 or loop.last %}
      {% endif %} -{% endfor %} + {% if loop.index0 % 2 == 1 or loop.last %}
      {% endif %} + {% endfor %} +{% endif %} {% for template in data.sessionTemplates %} @@ -139,25 +157,33 @@

      {% for slot in template.slots %} -
      - {{ slot.startLabel }} - to {{ slot.endLabel }} - - {% if slot.isNonBookable %} - Non-bookable{% if slot.nonBookableReason %}{{ slot.nonBookableReason }}{% endif %} - {% elseif slot.isSpecialAppointment %} - Special appointment - {% elseif slot.isHold %} - Held{% if slot.holdReason %}{{ slot.holdReason }}{% endif %} - {% endif %} - {% if slot.isCombined %} - {{ slot.span }} slots - {% endif %} - -
      - {% for position in slot.mergedChildren %} - - {% endfor %} +
      + + + + {{ slot.startLabel }} to {{ slot.endLabel }} + {% if slot.isCombined %} +
      {{ slot.span }} slots +
      {{ data.createSessionTemplate.duration * slot.span }} minutes + {% else %} +
      {{ data.createSessionTemplate.duration }} minutes + {% endif %} +
      + + +
      +
      + {% for position in slot.mergedChildren %} + + {% endfor %} {% endfor %}
      diff --git a/app/views/sessions/01-template-name.html b/app/views/sessions/01-template-name.html index 797a165..2a8a858 100644 --- a/app/views/sessions/01-template-name.html +++ b/app/views/sessions/01-template-name.html @@ -8,7 +8,11 @@ {{ primaryNavigation("Clinics", serviceName, bsoName) }} {% endblock %} -{% block beforeContent %}{% endblock %} +{% block beforeContent %} + + Back to clinic session templates + +{% endblock %} {% block content %} @@ -91,11 +95,11 @@

      -
      +
      diff --git a/app/views/sessions/02-session-times.html b/app/views/sessions/02-session-times.html index 109c16a..c80071c 100644 --- a/app/views/sessions/02-session-times.html +++ b/app/views/sessions/02-session-times.html @@ -162,9 +162,9 @@

      Session times

      -
      + {% if errors and errors.duration %} Error: {{ errors.duration }} diff --git a/app/views/sessions/03-slot-structure.html b/app/views/sessions/03-slot-structure.html index 1064bf7..778294b 100644 --- a/app/views/sessions/03-slot-structure.html +++ b/app/views/sessions/03-slot-structure.html @@ -107,14 +107,12 @@

      @@ -135,42 +133,48 @@

      Set slot types

      - Hold for: + Held

      • - +
      • - +
      • - +
      • - +
      • - +

      - Non-bookable as: + Non-bookable

      • - +
      • - +
      • - + +
      • +
      • + +
      • +
      • +

      - +

      @@ -185,59 +189,11 @@

    • - +
    -

    {{ bookableSlotCount }} bookable slots

    @@ -260,6 +216,8 @@

    var enhancedActions = [ 'mark-staff-break', 'mark-staff-meeting', + 'mark-admin', + 'mark-downtime', 'mark-non-bookable-generic', 'clear-slot-type', 'mark-hold-rebooking', diff --git a/app/views/sessions/04-check-template.html b/app/views/sessions/04-check-template.html index 5227aa2..a2ec5d1 100644 --- a/app/views/sessions/04-check-template.html +++ b/app/views/sessions/04-check-template.html @@ -92,48 +92,58 @@

    Check and confirm

    Slot structure
    - {% if data.createSessionTemplate.bookableBreakdown | length %} -
    - - - {{ data.createSessionTemplate.bookableSlotCount }} bookable slots - - -
    -
    - {% for item in data.createSessionTemplate.bookableBreakdown %} -
    -
    {{ item.label }}
    -
    {{ item.count }}
    -
    - {% endfor %} -
    -
    -
    - {% else %} -

    {{ data.createSessionTemplate.bookableSlotCount }} bookable slots

    - {% endif %} - {% if data.createSessionTemplate.nonBookableBreakdown | length %} -
    - - - {{ data.createSessionTemplate.nonBookableSlotCount }} non-bookable slots - - -
    -
    - {% for item in data.createSessionTemplate.nonBookableBreakdown %} -
    -
    {{ item.label }}
    -
    {{ item.count }}
    -
    - {% endfor %} -
    -
    -
    - {% else %} -

    {{ data.createSessionTemplate.nonBookableSlotCount }} non-bookable slots

    - {% endif %} + +
    +
    + {{ mapImageAlt }} +
    +
    + +

    {{ data.createSessionTemplate.standardSlotCount }} standard slots

    + + {% if data.createSessionTemplate.bookableBreakdown | length %} +
    + + + {{ data.createSessionTemplate.heldSlotCount }} held slots + + +
    +
    + {% for item in data.createSessionTemplate.bookableBreakdown %} +
    +
    {{ item.label | capitalize }}
    +
    {{ item.count }}
    +
    + {% endfor %} +
    +
    +
    + {% else %} + + {% endif %} + {% if data.createSessionTemplate.nonBookableBreakdown | length %} +
    + + + {{ data.createSessionTemplate.nonBookableSlotCount }} non-bookable slots + + +
    +
    + {% for item in data.createSessionTemplate.nonBookableBreakdown %} +
    +
    {{ item.label | capitalize }}
    +
    {{ item.count }}
    +
    + {% endfor %} +
    +
    +
    + {% else %} + + {% endif %} +
    Change slot structure