From 434d34f4e775cbd29e45d012338fabc67cfa18d2 Mon Sep 17 00:00:00 2001 From: Mat Johnson Date: Wed, 16 Sep 2026 17:40:13 +0100 Subject: [PATCH 1/3] First rough of adding and removing notes from a slot --- app/views/sessions/02-organise-slots.html | 50 ++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/app/views/sessions/02-organise-slots.html b/app/views/sessions/02-organise-slots.html index b90868a1..b40d50bb 100644 --- a/app/views/sessions/02-organise-slots.html +++ b/app/views/sessions/02-organise-slots.html @@ -25,6 +25,7 @@ .slot-end { font-size: 16px; margin-right: 1rem; } .slot-tag { font-size: 16px; margin-left: 1rem; } + .slot-note { font-size: 16px; margin-left: 1rem; } .slot.is-hold {} .slot.is-staff-break { @@ -182,6 +183,11 @@

With selected:

  • Merge selected
  • Unmerge
  • +
    +

    @@ -371,7 +377,8 @@

    With selected:

    merged: prev.merged || false, span: prev.span || 1, isMergedInto: prev.isMergedInto ?? undefined, - mergedEnd: prev.mergedEnd || null + mergedEnd: prev.mergedEnd || null, + note: prev.note || null }); } @@ -424,6 +431,7 @@

    With selected:

    const isSelected = selectedIndices.has(i) || (isChild && selectedIndices.has(headIndex)); const isMerged = sd.merged || isChild; const slotType = isChild ? headSlot?.type : sd.type; + const slotNote = isChild ? headSlot?.note : sd.note; const isLastMergedChild = isChild && headSlot && i === headIndex + (headSlot.span || 1) - 1; if (isSelected) classes.push('selected'); @@ -452,6 +460,11 @@

    With selected:

    typeHtml += `${typeLabel}`; } + let noteHtml = ''; + if (slotNote && !isChild) { + noteHtml += `Note: ${slotNote.replace(/"/g, '"')}`; + } + div.innerHTML = `
    @@ -459,6 +472,7 @@

    With selected:

    ${formatTime(sd.start)} to ${endTime} ${typeHtml} + ${noteHtml} ${mergedHtml}
    @@ -536,6 +550,8 @@

    With selected:

    document.getElementById('panel-clear-type').classList.toggle('disabled', !sel.length || !anyType); document.getElementById('panel-merge').classList.toggle('disabled', !canMerge); document.getElementById('panel-unmerge').classList.toggle('disabled', !hasMerged); + document.getElementById('panel-add-note').classList.toggle('disabled', !sel.length); + document.getElementById('panel-remove-note').classList.toggle('disabled', !sel.length); } // ========== EVENT ATTACHMENTS ========== @@ -546,6 +562,8 @@

    With selected:

    const panelClearType = document.getElementById('panel-clear-type'); const panelMerge = document.getElementById('panel-merge'); const panelUnmerge = document.getElementById('panel-unmerge'); + const panelAddNote = document.getElementById('panel-add-note'); + const panelRemoveNote = document.getElementById('panel-remove-note'); if (panelHold) { panelHold.addEventListener('click', () => { @@ -607,6 +625,36 @@

    With selected:

    }); } + if (panelAddNote) { + panelAddNote.addEventListener('click', () => { + syncSelectedIndicesFromCheckboxes(); + if (!selectedIndices.size) return; + // Prompt is left blank rather than pre-filled: some browsers (e.g. Firefox) don't + // auto-select a default value, so typing would append to it instead of replacing it. + const noteText = window.prompt('Add a note for the selected slots:'); + if (noteText === null || noteText.trim() === '') return; + const trimmedNote = noteText.trim(); + [...selectedIndices].forEach(i => { + if (slotData[i]) slotData[i].note = trimmedNote; + }); + selectedIndices.clear(); + renderGrid(); + setStats(); + }); + } + + if (panelRemoveNote) { + panelRemoveNote.addEventListener('click', () => { + syncSelectedIndicesFromCheckboxes(); + [...selectedIndices].forEach(i => { + if (slotData[i]) slotData[i].note = null; + }); + selectedIndices.clear(); + renderGrid(); + setStats(); + }); + } + // ========== INITIALIZATION ========== render(); From f54e038e7da91a93258a231fa15c9254b1d463d5 Mon Sep 17 00:00:00 2001 From: Mat Johnson Date: Wed, 16 Sep 2026 17:54:14 +0100 Subject: [PATCH 2/3] Change to an iffy --- app/views/sessions/02-organise-slots.html | 49 ++++++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/app/views/sessions/02-organise-slots.html b/app/views/sessions/02-organise-slots.html index b40d50bb..1e026362 100644 --- a/app/views/sessions/02-organise-slots.html +++ b/app/views/sessions/02-organise-slots.html @@ -65,6 +65,21 @@ .panel-item { display: block; cursor: pointer; } .panel-item.disabled { opacity: 0.38; cursor: default; pointer-events: none; } + + #note-dialog { + position: absolute; + width: 100%; + max-width: 400px; + border: 1px solid #d8dde0; + border-radius: 4px; + padding: 24px; + } + #note-dialog::backdrop { + background: rgba(0, 0, 0, 0.48); + } + #note-dialog .nhsuk-button { + margin-right: 8px; + } {% set tickSvg %} @@ -203,6 +218,19 @@

    With selected:

    + +
    +
    +

    + +

    + +
    + + +
    +
    + {% endblock %} {% block pageScripts %} @@ -564,6 +592,8 @@

    With selected:

    const panelUnmerge = document.getElementById('panel-unmerge'); const panelAddNote = document.getElementById('panel-add-note'); const panelRemoveNote = document.getElementById('panel-remove-note'); + const noteDialog = document.getElementById('note-dialog'); + const noteTextInput = document.getElementById('note-text'); if (panelHold) { panelHold.addEventListener('click', () => { @@ -629,13 +659,20 @@

    With selected:

    panelAddNote.addEventListener('click', () => { syncSelectedIndicesFromCheckboxes(); if (!selectedIndices.size) return; - // Prompt is left blank rather than pre-filled: some browsers (e.g. Firefox) don't - // auto-select a default value, so typing would append to it instead of replacing it. - const noteText = window.prompt('Add a note for the selected slots:'); - if (noteText === null || noteText.trim() === '') return; - const trimmedNote = noteText.trim(); + const notes = [...selectedIndices].map(i => slotData[i]?.note || ''); + noteTextInput.value = new Set(notes).size === 1 ? notes[0] : ''; + noteDialog.returnValue = ''; + noteDialog.showModal(); + }); + } + + if (noteDialog) { + noteDialog.addEventListener('close', () => { + if (noteDialog.returnValue !== 'save') return; + const noteText = noteTextInput.value.trim(); + if (!noteText) return; [...selectedIndices].forEach(i => { - if (slotData[i]) slotData[i].note = trimmedNote; + if (slotData[i]) slotData[i].note = noteText; }); selectedIndices.clear(); renderGrid(); From 2c232cd1b247b37f4d672b720b3d73eb565dc1e7 Mon Sep 17 00:00:00 2001 From: Mat Johnson Date: Thu, 17 Sep 2026 15:48:12 +0100 Subject: [PATCH 3/3] COMMIT milestone: this and the last couple of commits encompass a modal based note. We don't necessarily want to go this way. --- .../components/_template-preview-dialog.scss | 10 +++- .../_local-includes/template-preview.html | 8 +-- .../select-session-template.html | 8 +-- app/views/sessions/02-organise-slots.html | 50 +++++++++++-------- 4 files changed, 45 insertions(+), 31 deletions(-) diff --git a/app/assets/sass/components/_template-preview-dialog.scss b/app/assets/sass/components/_template-preview-dialog.scss index 9d9b9a91..3b110370 100644 --- a/app/assets/sass/components/_template-preview-dialog.scss +++ b/app/assets/sass/components/_template-preview-dialog.scss @@ -1,7 +1,7 @@ // Import NHS.UK frontend library @use "nhsuk-frontend/dist/nhsuk/core" as *; -.app-template-preview-dialog { +.app-dialog { position: fixed; inset: 0; width: 90vw; @@ -51,3 +51,11 @@ border: none; } } + +.app-template-preview-dialog { + +} + +.app-dialog-nano { + +} diff --git a/app/views/create-capacity-from-zero/_local-includes/template-preview.html b/app/views/create-capacity-from-zero/_local-includes/template-preview.html index 5dc43fc5..f5825d60 100644 --- a/app/views/create-capacity-from-zero/_local-includes/template-preview.html +++ b/app/views/create-capacity-from-zero/_local-includes/template-preview.html @@ -1,9 +1,9 @@ - -
    - - +
    \ No newline at end of file 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 91f28b8d..1c297bc3 100644 --- a/app/views/create-capacity-from-zero/select-session-template.html +++ b/app/views/create-capacity-from-zero/select-session-template.html @@ -225,13 +225,13 @@

    - -
    - - +
    {% 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 index 1e026362..d08b7072 100644 --- a/app/views/sessions/02-organise-slots.html +++ b/app/views/sessions/02-organise-slots.html @@ -200,7 +200,7 @@

    With selected:


    @@ -218,17 +218,23 @@

    With selected:

    - -
    -
    -

    - -

    - -
    - - -
    + +
    + +
    +
    +

    + +

    + +
    + + +
    +
    {% endblock %} @@ -590,9 +596,9 @@

    const panelClearType = document.getElementById('panel-clear-type'); const panelMerge = document.getElementById('panel-merge'); const panelUnmerge = document.getElementById('panel-unmerge'); - const panelAddNote = document.getElementById('panel-add-note'); const panelRemoveNote = document.getElementById('panel-remove-note'); const noteDialog = document.getElementById('note-dialog'); + const noteForm = document.getElementById('note-form'); const noteTextInput = document.getElementById('note-text'); if (panelHold) { @@ -655,28 +661,28 @@

    }); } - if (panelAddNote) { - panelAddNote.addEventListener('click', () => { + // Opening/closing the popover (trigger click, Cancel/close buttons, Escape, light-dismiss) is + // handled natively via popovertarget; JS only needs to sync app state around those events. + if (noteDialog) { + noteDialog.addEventListener('beforetoggle', event => { + if (event.newState !== 'open') return; syncSelectedIndicesFromCheckboxes(); - if (!selectedIndices.size) return; const notes = [...selectedIndices].map(i => slotData[i]?.note || ''); noteTextInput.value = new Set(notes).size === 1 ? notes[0] : ''; - noteDialog.returnValue = ''; - noteDialog.showModal(); }); } - if (noteDialog) { - noteDialog.addEventListener('close', () => { - if (noteDialog.returnValue !== 'save') return; + if (noteForm) { + noteForm.addEventListener('submit', () => { const noteText = noteTextInput.value.trim(); - if (!noteText) return; [...selectedIndices].forEach(i => { if (slotData[i]) slotData[i].note = noteText; }); selectedIndices.clear(); renderGrid(); setStats(); + // Firefox doesn't close a popover-dialog via the form's method="dialog" behaviour. + noteDialog.hidePopover(); }); }