diff --git a/app/routes/reading.js b/app/routes/reading.js index 4d53c37b..850e2425 100644 --- a/app/routes/reading.js +++ b/app/routes/reading.js @@ -285,6 +285,35 @@ module.exports = (router) => { res.redirect(`/reading/session/${req.params.sessionId}/your-reads`) }) + // Route for resuming a session — jumps straight into the next readable case, + // falling back to the session overview if none exists. + router.get('/reading/session/:sessionId/resume', (req, res) => { + const data = req.session.data + const { sessionId } = req.params + const session = getReadingSession(data, sessionId) + if (!session) { + return res.redirect('/reading') + } + + const sessionEvents = session.eventIds + .map((eventId) => data.events.find((e) => e.id === eventId)) + .filter(Boolean) + + const resumeEvent = getResumeEventForUser( + sessionEvents, + data.currentUser.id, + session.skippedEvents || [] + ) + + if (resumeEvent) { + return res.redirect( + `/reading/session/${sessionId}/events/${resumeEvent.id}` + ) + } + + res.redirect(`/reading/session/${sessionId}`) + }) + // Route for skipped-review page (shown at end of batch when skipped cases remain) router.get('/reading/session/:sessionId/skipped-review', (req, res) => { const data = req.session.data diff --git a/app/views/_templates/layout-base.html b/app/views/_templates/layout-base.html index 849d3d86..bc57ae2e 100644 --- a/app/views/_templates/layout-base.html +++ b/app/views/_templates/layout-base.html @@ -43,7 +43,7 @@ current: true if navActive == "screening" }, { - href: "/reading", + href: "/reading/index-simple", text: "Image reading", current: true if navActive == "reading" }, diff --git a/app/views/reading/index-complex.html b/app/views/reading/index-complex.html new file mode 100644 index 00000000..2ec79f03 --- /dev/null +++ b/app/views/reading/index-complex.html @@ -0,0 +1,187 @@ +{# app/views/events/image-reading/index.html #} + +{% extends 'layout-app.html' %} + +{% set pageHeading = "Image reading" %} +{% set hideBackLink = true %} +{% set gridColumn = "nhsuk-grid-column-full" %} +{% set showRemaining = data.settings.reading.showRemaining | falsify %} + +{% block pageContent %} +

{{ pageHeading }}

+ +

Start new reading session

+ +{# Awaiting priors not automatically filtered #} +{% set allReadsEventsWithAwaitingPriors = data.events | filterEventsByEligibleForReading | filterEventsByNeedsAnyRead | sortEventsByScreeningDate %} + +{# Split into awaiting priors and available for reading #} +{# Events with 'requested' status mammograms are held from reading #} +{% set allReadsEvents = [] %} +{% set awaitingPriorsEvents = [] %} +{% for thisEvent in allReadsEventsWithAwaitingPriors %} + {% if thisEvent | awaitingPriors %} + {% set awaitingPriorsEvents = awaitingPriorsEvents | push(thisEvent) %} + {% else %} + {% set allReadsEvents = allReadsEvents | push(thisEvent) %} + {% endif %} +{% endfor %} + +{% set recentEvents = allReadsEvents | filterEventsByDayRange(0, data.config.reading.priorityThreshold) %} + +{% set priorityEvents = allReadsEvents | filterEventsByDayRange(data.config.reading.priorityThreshold, data.config.reading.urgentThreshold - 1) %} +{% set urgentEvents = allReadsEvents | filterEventsByDayRange(data.config.reading.urgentThreshold) %} + + + +{% set firstReadsEvents = allReadsEvents | filterEventsByNeedsFirstRead %} +{% set secondReadsEvents = allReadsEvents | filterEventsByNeedsSecondRead %} + +{# Filter for current user - currently not used #} +{% set allReadsForUser = allReadsEvents | filterEventsByUserCanRead(data.currentUser.id) %} +{% set firstReadsForUser = firstReadsEvents | filterEventsByUserCanRead(data.currentUser.id) %} +{% set secondReadsForUser = secondReadsEvents | filterEventsByUserCanRead(data.currentUser.id) %} +{% set awaitingPriorsForUser = awaitingPriorsEvents | filterEventsByUserCanRead(data.currentUser.id) %} + +{# Get oldest #} +{% set oldestAllRead = allReadsEvents[0].timing.startTime if allReadsEvents.length > 0 %} +{% set oldestFirstRead = firstReadsEvents[0].timing.startTime if firstReadsEvents.length > 0 %} +{% set oldestSecondRead = secondReadsEvents[0].timing.startTime if secondReadsEvents.length > 0 %} +{% set oldestAwaitingPrior = awaitingPriorsEvents[0].timing.startTime if awaitingPriorsEvents.length > 0 %} + +{# All reads card content #} +{% set allReadsContent %} + + {% set allReadCount = allReadsEvents | length %} + + {% set hasPriortyTags = priorityEvents or urgentEvents %} +

+ {{ allReadCount }} cases needing reading +

+ + {% if hasPriortyTags %} +
+ {% if urgentEvents | length %} + {{ ((urgentEvents | length) ~ " urgent cases") | toTag({colour: "red"}) }} + {% endif %} + {% if priorityEvents | length %} + {{ ((priorityEvents | length) ~ " cases due soon") | toTag({colour: "orange"}) }} + {% endif %} +
+ {% endif %} + + {% set defaultSessionSize = 25 %} + {% set maxCases = allReadCount if allReadCount < defaultSessionSize else defaultSessionSize %} + + {% set actionLinkHtml %} + Start session
+ + ({{ maxCases }} cases) + + {% endset %} + {{ actionLink({ + classes: "nhsuk-link--no-visited-state nhsuk-u-margin-top-2", + text: actionLinkHtml | safe, + href: "/reading/create-session?type=all_reads" + }) }} + +{% endset %} + +{# Custom session card content #} +{% set customSessionContent %} +

Choose what cases to read

+{{ actionLink({ + classes: "nhsuk-link--no-visited-state nhsuk-u-margin-top-2", + text: "Start custom session", + href: "/reading/create-custom-session" +}) }} +{% endset %} + +{# Arbitration card content #} +{% set arbitrationContent %} +

53 cases need arbitration

+{{ actionLink({ + classes: "nhsuk-link--no-visited-state nhsuk-u-margin-top-2", + text: "See cases", + href: "#" +}) }} +{% endset %} + +{# Reading history card content #} +{% set readingHistoryContent %} +

View your reading history and all recently read cases

+{{ actionLink({ + classes: "nhsuk-link--no-visited-state nhsuk-u-margin-top-2", + text: "View history", + href: "/reading/history" +}) }} +{% endset %} + + + +

Other options

+ + + +{% endblock %} \ No newline at end of file diff --git a/app/views/reading/index-simple.html b/app/views/reading/index-simple.html new file mode 100644 index 00000000..744dc7c5 --- /dev/null +++ b/app/views/reading/index-simple.html @@ -0,0 +1,172 @@ +{# app/views/reading/index.html #} + +{% extends 'layout-app.html' %} + +{% set pageHeading = "Image reading" %} +{% set hideBackLink = true %} +{% set gridColumn = "none" %} + +{% set currentUserId = data.currentUser.id %} +{% set defaultSessionSize = 25 %} + +{# + Build a list of the current user's sessions, most-recent first. + A session counts as "the user's" if they've already read at least one case in it. + Used both to detect an in-progress session and to list previous sessions. +#} +{% set userSessions = [] %} +{% for sessionId, session in data.readingSessions %} + {% set userReadCount = 0 %} + {% for eventId in session.eventIds %} + {% set thisEvent = data.events | find('id', eventId) %} + {% if thisEvent and (thisEvent | userHasReadEvent(currentUserId)) %} + {% set userReadCount = userReadCount + 1 %} + {% endif %} + {% endfor %} + {% if userReadCount > 0 %} + {% set targetSize = session.targetSize or session.eventIds | length %} + {% set userSessions = userSessions | push({ + id: session.id, + createdAt: session.createdAt, + targetSize: targetSize, + userReadCount: userReadCount, + isComplete: userReadCount >= targetSize + }) %} + {% endif %} +{% endfor %} + +{% set userSessions = userSessions | sort(true, false, 'createdAt') %} + +{# In-progress = the user's most recent session that isn't complete #} +{% set inProgressSession = null %} +{% for session in userSessions %} + {% if not inProgressSession and not session.isComplete %} + {% set inProgressSession = session %} + {% endif %} +{% endfor %} + +{# Previous (completed) sessions — show 3 most recent #} +{% set previousSessions = [] %} +{% for session in userSessions %} + {% if session.isComplete and previousSessions | length < 3 %} + {% set previousSessions = previousSessions | push(session) %} + {% endif %} +{% endfor %} + +{# + Inset counts. + + arbitrationCount is derived from real data — events whose computed outcome is + arbitration_pending. + + newlyArrivedPriorsCount is faked for the prototype. In a real implementation + this would be the count of cases where priors the reader requested or was + waiting on have since arrived. Edit the value below (or set it to 0) to see + the empty state of the inset. +#} +{% set arbitrationCount = 0 %} +{% for thisEvent in data.events %} + {% if (thisEvent | getOutcome) == 'arbitration_pending' %} + {% set arbitrationCount = arbitrationCount + 1 %} + {% endif %} +{% endfor %} + +{% set newlyArrivedPriorsCount = 2 %} + +{% set actionTotal = arbitrationCount + newlyArrivedPriorsCount %} + +{# + Backlog counts — all eligible cases that still need reading, banded by + age relative to the configured priority/urgent thresholds. +#} +{% set backlogEvents = data.events + | filterEventsByEligibleForReading + | filterEventsByNeedsAnyRead %} +{% set backlogTotal = backlogEvents | length %} +{% set urgentBacklog = backlogEvents | filterEventsByDayRange(data.config.reading.urgentThreshold) | length %} +{% set priorityBacklog = backlogEvents | filterEventsByDayRange(data.config.reading.priorityThreshold, data.config.reading.urgentThreshold - 1) | length %} + +{% block pageContent %} +
+
+

{{ pageHeading }}

+ + {% if inProgressSession %} + +

Resume image reading

+

You have an image reading session in progress. Finish it before starting a new one.

+

{{ inProgressSession.userReadCount }} of {{ inProgressSession.targetSize }} cases read.

+ + {{ button({ + text: "Resume session", + href: "/reading/session/" + inProgressSession.id + "/resume" + }) }} + + {% else %} + +

Start image reading

+

Start a new image reading session with {{ defaultSessionSize }} cases.

+ + {{ button({ + text: "Start now", + href: "/reading/create-session?type=all_reads" + }) }} + + {% endif %} + + {% if actionTotal > 0 %} + {% set insetHtml %} +

{{ actionTotal }} flagged {{ "case" if actionTotal == 1 else "cases" }}

+ + {% endset %} + {{ insetText({ + html: insetHtml | safe + }) }} + {% endif %} + + {% if previousSessions | length > 0 %} +

Your previous image reading sessions

+ +

See all

+ {% endif %} + +
+ +
+ {% set backlogCardHtml %} +

{{ backlogTotal }} cases require reading

+ + {% endset %} + + {{ card({ + heading: "Backlog", + headingLevel: "2", + feature: true, + descriptionHtml: backlogCardHtml + }) }} +
+
+ +{% endblock %} \ No newline at end of file diff --git a/app/views/reading/index.html b/app/views/reading/index.html index 2ec79f03..dccc68a9 100644 --- a/app/views/reading/index.html +++ b/app/views/reading/index.html @@ -1,187 +1,172 @@ -{# app/views/events/image-reading/index.html #} +{# app/views/reading/index.html #} {% extends 'layout-app.html' %} {% set pageHeading = "Image reading" %} {% set hideBackLink = true %} -{% set gridColumn = "nhsuk-grid-column-full" %} -{% set showRemaining = data.settings.reading.showRemaining | falsify %} - -{% block pageContent %} -

{{ pageHeading }}

- -

Start new reading session

- -{# Awaiting priors not automatically filtered #} -{% set allReadsEventsWithAwaitingPriors = data.events | filterEventsByEligibleForReading | filterEventsByNeedsAnyRead | sortEventsByScreeningDate %} - -{# Split into awaiting priors and available for reading #} -{# Events with 'requested' status mammograms are held from reading #} -{% set allReadsEvents = [] %} -{% set awaitingPriorsEvents = [] %} -{% for thisEvent in allReadsEventsWithAwaitingPriors %} - {% if thisEvent | awaitingPriors %} - {% set awaitingPriorsEvents = awaitingPriorsEvents | push(thisEvent) %} - {% else %} - {% set allReadsEvents = allReadsEvents | push(thisEvent) %} +{% set gridColumn = "none" %} + +{% set currentUserId = data.currentUser.id %} +{% set defaultSessionSize = 25 %} + +{# + Build a list of the current user's sessions, most-recent first. + A session counts as "the user's" if they've already read at least one case in it. + Used both to detect an in-progress session and to list previous sessions. +#} +{% set userSessions = [] %} +{% for sessionId, session in data.readingSessions %} + {% set userReadCount = 0 %} + {% for eventId in session.eventIds %} + {% set thisEvent = data.events | find('id', eventId) %} + {% if thisEvent and (thisEvent | userHasReadEvent(currentUserId)) %} + {% set userReadCount = userReadCount + 1 %} + {% endif %} + {% endfor %} + {% if userReadCount > 0 %} + {% set targetSize = session.targetSize or session.eventIds | length %} + {% set userSessions = userSessions | push({ + id: session.id, + createdAt: session.createdAt, + targetSize: targetSize, + userReadCount: userReadCount, + isComplete: userReadCount >= targetSize + }) %} {% endif %} {% endfor %} -{% set recentEvents = allReadsEvents | filterEventsByDayRange(0, data.config.reading.priorityThreshold) %} - -{% set priorityEvents = allReadsEvents | filterEventsByDayRange(data.config.reading.priorityThreshold, data.config.reading.urgentThreshold - 1) %} -{% set urgentEvents = allReadsEvents | filterEventsByDayRange(data.config.reading.urgentThreshold) %} - +{% set userSessions = userSessions | sort(true, false, 'createdAt') %} +{# In-progress = the user's most recent session that isn't complete #} +{% set inProgressSession = null %} +{% for session in userSessions %} + {% if not inProgressSession and not session.isComplete %} + {% set inProgressSession = session %} + {% endif %} +{% endfor %} -{% set firstReadsEvents = allReadsEvents | filterEventsByNeedsFirstRead %} -{% set secondReadsEvents = allReadsEvents | filterEventsByNeedsSecondRead %} - -{# Filter for current user - currently not used #} -{% set allReadsForUser = allReadsEvents | filterEventsByUserCanRead(data.currentUser.id) %} -{% set firstReadsForUser = firstReadsEvents | filterEventsByUserCanRead(data.currentUser.id) %} -{% set secondReadsForUser = secondReadsEvents | filterEventsByUserCanRead(data.currentUser.id) %} -{% set awaitingPriorsForUser = awaitingPriorsEvents | filterEventsByUserCanRead(data.currentUser.id) %} +{# Previous (completed) sessions — show 3 most recent #} +{% set previousSessions = [] %} +{% for session in userSessions %} + {% if session.isComplete and previousSessions | length < 3 %} + {% set previousSessions = previousSessions | push(session) %} + {% endif %} +{% endfor %} -{# Get oldest #} -{% set oldestAllRead = allReadsEvents[0].timing.startTime if allReadsEvents.length > 0 %} -{% set oldestFirstRead = firstReadsEvents[0].timing.startTime if firstReadsEvents.length > 0 %} -{% set oldestSecondRead = secondReadsEvents[0].timing.startTime if secondReadsEvents.length > 0 %} -{% set oldestAwaitingPrior = awaitingPriorsEvents[0].timing.startTime if awaitingPriorsEvents.length > 0 %} +{# + Inset counts. + + arbitrationCount is derived from real data — events whose computed outcome is + arbitration_pending. + + newlyArrivedPriorsCount is faked for the prototype. In a real implementation + this would be the count of cases where priors the reader requested or was + waiting on have since arrived. Edit the value below (or set it to 0) to see + the empty state of the inset. +#} +{% set arbitrationCount = 0 %} +{% for thisEvent in data.events %} + {% if (thisEvent | getOutcome) == 'arbitration_pending' %} + {% set arbitrationCount = arbitrationCount + 1 %} + {% endif %} +{% endfor %} -{# All reads card content #} -{% set allReadsContent %} +{% set newlyArrivedPriorsCount = 2 %} - {% set allReadCount = allReadsEvents | length %} +{% set actionTotal = arbitrationCount + newlyArrivedPriorsCount %} - {% set hasPriortyTags = priorityEvents or urgentEvents %} -

- {{ allReadCount }} cases needing reading -

+{# + Backlog counts — all eligible cases that still need reading, banded by + age relative to the configured priority/urgent thresholds. +#} +{% set backlogEvents = data.events + | filterEventsByEligibleForReading + | filterEventsByNeedsAnyRead %} +{% set backlogTotal = backlogEvents | length %} +{% set urgentBacklog = backlogEvents | filterEventsByDayRange(data.config.reading.urgentThreshold) | length %} +{% set priorityBacklog = backlogEvents | filterEventsByDayRange(data.config.reading.priorityThreshold, data.config.reading.urgentThreshold - 1) | length %} - {% if hasPriortyTags %} -
- {% if urgentEvents | length %} - {{ ((urgentEvents | length) ~ " urgent cases") | toTag({colour: "red"}) }} - {% endif %} - {% if priorityEvents | length %} - {{ ((priorityEvents | length) ~ " cases due soon") | toTag({colour: "orange"}) }} - {% endif %} -
+{% block pageContent %} +
+
+

{{ pageHeading }}

+ + {% if actionTotal > 0 %} + {% set insetHtml %} +

{{ actionTotal }} {{ "case" if actionTotal == 1 else "cases" }} need action

+ + {% endset %} + {{ insetText({ + html: insetHtml | safe + }) }} {% endif %} - {% set defaultSessionSize = 25 %} - {% set maxCases = allReadCount if allReadCount < defaultSessionSize else defaultSessionSize %} - - {% set actionLinkHtml %} - Start session
- - ({{ maxCases }} cases) - - {% endset %} - {{ actionLink({ - classes: "nhsuk-link--no-visited-state nhsuk-u-margin-top-2", - text: actionLinkHtml | safe, - href: "/reading/create-session?type=all_reads" - }) }} - -{% endset %} - -{# Custom session card content #} -{% set customSessionContent %} -

Choose what cases to read

-{{ actionLink({ - classes: "nhsuk-link--no-visited-state nhsuk-u-margin-top-2", - text: "Start custom session", - href: "/reading/create-custom-session" -}) }} -{% endset %} - -{# Arbitration card content #} -{% set arbitrationContent %} -

53 cases need arbitration

-{{ actionLink({ - classes: "nhsuk-link--no-visited-state nhsuk-u-margin-top-2", - text: "See cases", - href: "#" -}) }} -{% endset %} - -{# Reading history card content #} -{% set readingHistoryContent %} -

View your reading history and all recently read cases

-{{ actionLink({ - classes: "nhsuk-link--no-visited-state nhsuk-u-margin-top-2", - text: "View history", - href: "/reading/history" -}) }} -{% endset %} - - +

Start image reading

+

Start a new image reading session with {{ defaultSessionSize }} cases.

-

Other options

- + {% endif %} + + {% if previousSessions | length > 0 %} +

Your previous image reading sessions

+ +

See all

+ {% endif %} +
+ +
+ {% set backlogCardHtml %} +

{{ backlogTotal }} cases require reading

+ + {% endset %} + + {{ card({ + heading: "Backlog", + headingLevel: "2", + feature: true, + descriptionHtml: backlogCardHtml + }) }} +
+
{% endblock %} \ No newline at end of file